PromiseConstructor.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. using Jint.Collections;
  2. using Jint.Native.Function;
  3. using Jint.Native.Iterator;
  4. using Jint.Native.Object;
  5. using Jint.Native.Symbol;
  6. using Jint.Runtime;
  7. using Jint.Runtime.Descriptors;
  8. using Jint.Runtime.Interop;
  9. namespace Jint.Native.Promise;
  10. internal sealed record PromiseCapability(
  11. JsValue PromiseInstance,
  12. ICallable Resolve,
  13. ICallable Reject,
  14. JsValue ResolveObj,
  15. JsValue RejectObj);
  16. internal sealed class PromiseConstructor : Constructor
  17. {
  18. private static readonly JsString _functionName = new JsString("Promise");
  19. internal PromiseConstructor(
  20. Engine engine,
  21. Realm realm,
  22. FunctionPrototype functionPrototype,
  23. ObjectPrototype objectPrototype)
  24. : base(engine, realm, _functionName)
  25. {
  26. _prototype = functionPrototype;
  27. PrototypeObject = new PromisePrototype(engine, realm, this, objectPrototype);
  28. _length = new PropertyDescriptor(1, PropertyFlag.Configurable);
  29. _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
  30. }
  31. internal PromisePrototype PrototypeObject { get; }
  32. protected override void Initialize()
  33. {
  34. const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
  35. const PropertyFlag LengthFlags = PropertyFlag.Configurable;
  36. var properties = new PropertyDictionary(8, checkExistingKeys: false)
  37. {
  38. ["all"] = new(new PropertyDescriptor(new ClrFunction(Engine, "all", All, 1, LengthFlags), PropertyFlags)),
  39. ["allSettled"] = new(new PropertyDescriptor(new ClrFunction(Engine, "allSettled", AllSettled, 1, LengthFlags), PropertyFlags)),
  40. ["any"] = new(new PropertyDescriptor(new ClrFunction(Engine, "any", Any, 1, LengthFlags), PropertyFlags)),
  41. ["race"] = new(new PropertyDescriptor(new ClrFunction(Engine, "race", Race, 1, LengthFlags), PropertyFlags)),
  42. ["reject"] = new(new PropertyDescriptor(new ClrFunction(Engine, "reject", Reject, 1, LengthFlags), PropertyFlags)),
  43. ["resolve"] = new(new PropertyDescriptor(new ClrFunction(Engine, "resolve", Resolve, 1, LengthFlags), PropertyFlags)),
  44. ["try"] = new(new PropertyDescriptor(new ClrFunction(Engine, "try", Try, 1, LengthFlags), PropertyFlags)),
  45. ["withResolvers"] = new(new PropertyDescriptor(new ClrFunction(Engine, "withResolvers", WithResolvers , 0, LengthFlags), PropertyFlags)),
  46. };
  47. SetProperties(properties);
  48. var symbols = new SymbolDictionary(1)
  49. {
  50. [GlobalSymbolRegistry.Species] = new GetSetPropertyDescriptor(
  51. get: new ClrFunction(_engine, "get [Symbol.species]", (thisObj, _) => thisObj, 0, PropertyFlag.Configurable),
  52. set: Undefined, PropertyFlag.Configurable)
  53. };
  54. SetSymbols(symbols);
  55. }
  56. /// <summary>
  57. /// https://tc39.es/ecma262/#sec-promise-executor
  58. /// </summary>
  59. public override ObjectInstance Construct(JsCallArguments arguments, JsValue newTarget)
  60. {
  61. if (newTarget.IsUndefined())
  62. {
  63. ExceptionHelper.ThrowTypeError(_realm, "Constructor Promise requires 'new'");
  64. }
  65. if (arguments.At(0) is not ICallable executor)
  66. {
  67. ExceptionHelper.ThrowTypeError(_realm, $"Promise executor {(arguments.At(0))} is not a function");
  68. return null;
  69. }
  70. var promise = OrdinaryCreateFromConstructor(
  71. newTarget,
  72. static intrinsics => intrinsics.Promise.PrototypeObject,
  73. static (Engine engine, Realm _, object? _) => new JsPromise(engine));
  74. var (resolve, reject) = promise.CreateResolvingFunctions();
  75. try
  76. {
  77. executor.Call(Undefined, resolve, reject);
  78. }
  79. catch (JavaScriptException e)
  80. {
  81. reject.Call(JsValue.Undefined, [e.Error]);
  82. }
  83. return promise;
  84. }
  85. /// <summary>
  86. /// https://tc39.es/ecma262/#sec-promise.resolve
  87. /// </summary>
  88. internal JsValue Resolve(JsValue thisObject, JsCallArguments arguments)
  89. {
  90. if (!thisObject.IsObject())
  91. {
  92. ExceptionHelper.ThrowTypeError(_realm, "PromiseResolve called on non-object");
  93. }
  94. if (thisObject is not IConstructor)
  95. {
  96. ExceptionHelper.ThrowTypeError(_realm, "Promise.resolve invoked on a non-constructor value");
  97. }
  98. var x = arguments.At(0);
  99. return PromiseResolve(thisObject, x);
  100. }
  101. private JsObject WithResolvers(JsValue thisObject, JsCallArguments arguments)
  102. {
  103. var promiseCapability = NewPromiseCapability(_engine, thisObject);
  104. var obj = OrdinaryObjectCreate(_engine, _engine.Realm.Intrinsics.Object.PrototypeObject);
  105. obj.CreateDataPropertyOrThrow("promise", promiseCapability.PromiseInstance);
  106. obj.CreateDataPropertyOrThrow("resolve", promiseCapability.ResolveObj);
  107. obj.CreateDataPropertyOrThrow("reject", promiseCapability.RejectObj);
  108. return obj;
  109. }
  110. /// <summary>
  111. /// https://tc39.es/ecma262/#sec-promise-resolve
  112. /// </summary>
  113. private JsValue PromiseResolve(JsValue thisObject, JsValue x)
  114. {
  115. if (x.IsPromise())
  116. {
  117. var xConstructor = x.Get(CommonProperties.Constructor);
  118. if (SameValue(xConstructor, thisObject))
  119. {
  120. return x;
  121. }
  122. }
  123. var capability = NewPromiseCapability(_engine, thisObject);
  124. capability.Resolve.Call(Undefined, x);
  125. return capability.PromiseInstance;
  126. }
  127. /// <summary>
  128. /// https://tc39.es/ecma262/#sec-promise.reject
  129. /// </summary>
  130. private JsValue Reject(JsValue thisObject, JsCallArguments arguments)
  131. {
  132. if (!thisObject.IsObject())
  133. {
  134. ExceptionHelper.ThrowTypeError(_realm, "Promise.reject called on non-object");
  135. }
  136. if (thisObject is not IConstructor)
  137. {
  138. ExceptionHelper.ThrowTypeError(_realm, "Promise.reject invoked on a non-constructor value");
  139. }
  140. var r = arguments.At(0);
  141. var capability = NewPromiseCapability(_engine, thisObject);
  142. capability.Reject.Call(Undefined, r);
  143. return capability.PromiseInstance;
  144. }
  145. /// <summary>
  146. /// https://tc39.es/proposal-promise-try/
  147. /// </summary>
  148. private JsValue Try(JsValue thisObject, JsCallArguments arguments)
  149. {
  150. if (!thisObject.IsObject())
  151. {
  152. ExceptionHelper.ThrowTypeError(_realm, "Promise.try called on non-object");
  153. }
  154. var callbackfn = arguments.At(0);
  155. var promiseCapability = NewPromiseCapability(_engine, thisObject);
  156. try
  157. {
  158. var status = callbackfn.Call(Undefined, arguments.AsSpan().Slice(1).ToArray());
  159. promiseCapability.Resolve.Call(Undefined, status);
  160. }
  161. catch (JavaScriptException e)
  162. {
  163. promiseCapability.Reject.Call(Undefined, e.Error);
  164. }
  165. return promiseCapability.PromiseInstance;
  166. }
  167. // This helper methods executes the first 6 steps in the specs belonging to static Promise methods like all, any etc.
  168. // If it returns false, that means it has an error and it is already rejected
  169. // If it returns true, the logic specific to the calling function should continue executing
  170. private bool TryGetPromiseCapabilityAndIterator(JsValue thisObject, JsCallArguments arguments, string callerName, out PromiseCapability capability, out ICallable promiseResolve, out IteratorInstance iterator)
  171. {
  172. if (!thisObject.IsObject())
  173. {
  174. ExceptionHelper.ThrowTypeError(_realm, $"{callerName} called on non-object");
  175. }
  176. //2. Let promiseCapability be ? NewPromiseCapability(C).
  177. capability = NewPromiseCapability(_engine, thisObject);
  178. var reject = capability.Reject;
  179. //3. Let promiseResolve be GetPromiseResolve(C).
  180. // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
  181. try
  182. {
  183. promiseResolve = GetPromiseResolve(thisObject);
  184. }
  185. catch (JavaScriptException e)
  186. {
  187. reject.Call(Undefined, e.Error);
  188. promiseResolve = null!;
  189. iterator = null!;
  190. return false;
  191. }
  192. // 5. Let iteratorRecord be GetIterator(iterable).
  193. // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
  194. try
  195. {
  196. if (arguments.Length == 0)
  197. {
  198. ExceptionHelper.ThrowTypeError(_realm, $"no arguments were passed to {callerName}");
  199. }
  200. var iterable = arguments.At(0);
  201. iterator = iterable.GetIterator(_realm);
  202. }
  203. catch (JavaScriptException e)
  204. {
  205. reject.Call(Undefined, e.Error);
  206. iterator = null!;
  207. return false;
  208. }
  209. return true;
  210. }
  211. // https://tc39.es/ecma262/#sec-promise.all
  212. private JsValue All(JsValue thisObject, JsCallArguments arguments)
  213. {
  214. if (!TryGetPromiseCapabilityAndIterator(thisObject, arguments, "Promise.all", out var capability, out var promiseResolve, out var iterator))
  215. return capability.PromiseInstance;
  216. var results = new List<JsValue>();
  217. bool doneIterating = false;
  218. void ResolveIfFinished()
  219. {
  220. // that means all of them were resolved
  221. // Note that "Undefined" is not null, thus the logic is sound, even though awkward
  222. // also note that it is important to check if we are done iterating.
  223. // if "then" method is sync then it will be resolved BEFORE the next iteration cycle
  224. if (results.TrueForAll(static x => x is not null) && doneIterating)
  225. {
  226. var array = _realm.Intrinsics.Array.ConstructFast(results);
  227. capability.Resolve.Call(Undefined, array);
  228. }
  229. }
  230. // 27.2.4.1.2 PerformPromiseAll ( iteratorRecord, constructor, resultCapability, promiseResolve )
  231. // https://tc39.es/ecma262/#sec-performpromiseall
  232. try
  233. {
  234. int index = 0;
  235. do
  236. {
  237. JsValue value;
  238. try
  239. {
  240. if (!iterator.TryIteratorStep(out var nextItem))
  241. {
  242. doneIterating = true;
  243. ResolveIfFinished();
  244. break;
  245. }
  246. value = nextItem.Get(CommonProperties.Value);
  247. }
  248. catch (JavaScriptException e)
  249. {
  250. capability.Reject.Call(Undefined, e.Error);
  251. return capability.PromiseInstance;
  252. }
  253. // note that null here is important
  254. // it will help to detect if all inner promises were resolved
  255. // In F# it would be Option<JsValue>
  256. results.Add(null!);
  257. var item = promiseResolve.Call(thisObject, value);
  258. var thenProps = item.Get("then");
  259. if (thenProps is ICallable thenFunc)
  260. {
  261. var capturedIndex = index;
  262. var alreadyCalled = false;
  263. var onSuccess =
  264. new ClrFunction(_engine, "", (_, args) =>
  265. {
  266. if (!alreadyCalled)
  267. {
  268. alreadyCalled = true;
  269. results[capturedIndex] = args.At(0);
  270. ResolveIfFinished();
  271. }
  272. return Undefined;
  273. }, 1, PropertyFlag.Configurable);
  274. thenFunc.Call(item, onSuccess, capability.RejectObj);
  275. }
  276. else
  277. {
  278. ExceptionHelper.ThrowTypeError(_realm, "Passed non Promise-like value");
  279. }
  280. index += 1;
  281. } while (true);
  282. }
  283. catch (JavaScriptException e)
  284. {
  285. iterator.Close(CompletionType.Throw);
  286. capability.Reject.Call(Undefined, e.Error);
  287. return capability.PromiseInstance;
  288. }
  289. return capability.PromiseInstance;
  290. }
  291. // https://tc39.es/ecma262/#sec-promise.allsettled
  292. private JsValue AllSettled(JsValue thisObject, JsCallArguments arguments)
  293. {
  294. if (!TryGetPromiseCapabilityAndIterator(thisObject, arguments, "Promise.allSettled", out var capability, out var promiseResolve, out var iterator))
  295. return capability.PromiseInstance;
  296. var results = new List<JsValue>();
  297. bool doneIterating = false;
  298. void ResolveIfFinished()
  299. {
  300. // that means all of them were resolved
  301. // Note that "Undefined" is not null, thus the logic is sound, even though awkward
  302. // also note that it is important to check if we are done iterating.
  303. // if "then" method is sync then it will be resolved BEFORE the next iteration cycle
  304. if (results.TrueForAll(static x => x is not null) && doneIterating)
  305. {
  306. var array = _realm.Intrinsics.Array.ConstructFast(results);
  307. capability.Resolve.Call(Undefined, array);
  308. }
  309. }
  310. // 27.2.4.1.2 PerformPromiseAll ( iteratorRecord, constructor, resultCapability, promiseResolve )
  311. // https://tc39.es/ecma262/#sec-performpromiseall
  312. try
  313. {
  314. int index = 0;
  315. do
  316. {
  317. JsValue value;
  318. try
  319. {
  320. if (!iterator.TryIteratorStep(out var nextItem))
  321. {
  322. doneIterating = true;
  323. ResolveIfFinished();
  324. break;
  325. }
  326. value = nextItem.Get(CommonProperties.Value);
  327. }
  328. catch (JavaScriptException e)
  329. {
  330. capability.Reject.Call(Undefined, e.Error);
  331. return capability.PromiseInstance;
  332. }
  333. // note that null here is important
  334. // it will help to detect if all inner promises were resolved
  335. // In F# it would be Option<JsValue>
  336. results.Add(null!);
  337. var item = promiseResolve.Call(thisObject, value);
  338. var thenProps = item.Get("then");
  339. if (thenProps is ICallable thenFunc)
  340. {
  341. var capturedIndex = index;
  342. var alreadyCalled = false;
  343. var onSuccess =
  344. new ClrFunction(_engine, "", (_, args) =>
  345. {
  346. if (!alreadyCalled)
  347. {
  348. alreadyCalled = true;
  349. var res = Engine.Realm.Intrinsics.Object.Construct(2);
  350. res.FastSetDataProperty("status", "fulfilled");
  351. res.FastSetDataProperty("value", args.At(0));
  352. results[capturedIndex] = res;
  353. ResolveIfFinished();
  354. }
  355. return Undefined;
  356. }, 1, PropertyFlag.Configurable);
  357. var onFailure =
  358. new ClrFunction(_engine, "", (_, args) =>
  359. {
  360. if (!alreadyCalled)
  361. {
  362. alreadyCalled = true;
  363. var res = Engine.Realm.Intrinsics.Object.Construct(2);
  364. res.FastSetDataProperty("status", "rejected");
  365. res.FastSetDataProperty("reason", args.At(0));
  366. results[capturedIndex] = res;
  367. ResolveIfFinished();
  368. }
  369. return Undefined;
  370. }, 1, PropertyFlag.Configurable);
  371. thenFunc.Call(item, onSuccess, onFailure);
  372. }
  373. else
  374. {
  375. ExceptionHelper.ThrowTypeError(_realm, "Passed non Promise-like value");
  376. }
  377. index += 1;
  378. } while (true);
  379. }
  380. catch (JavaScriptException e)
  381. {
  382. iterator.Close(CompletionType.Throw);
  383. capability.Reject.Call(Undefined, e.Error);
  384. return capability.PromiseInstance;
  385. }
  386. return capability.PromiseInstance;
  387. }
  388. // https://tc39.es/ecma262/#sec-promise.any
  389. private JsValue Any(JsValue thisObject, JsCallArguments arguments)
  390. {
  391. if (!TryGetPromiseCapabilityAndIterator(thisObject, arguments, "Promise.any", out var capability, out var promiseResolve, out var iterator))
  392. {
  393. return capability.PromiseInstance;
  394. }
  395. var errors = new List<JsValue>();
  396. var doneIterating = false;
  397. void RejectIfAllRejected()
  398. {
  399. // that means all of them were rejected
  400. // Note that "Undefined" is not null, thus the logic is sound, even though awkward
  401. // also note that it is important to check if we are done iterating.
  402. // if "then" method is sync then it will be resolved BEFORE the next iteration cycle
  403. if (errors.TrueForAll(static x => x is not null) && doneIterating)
  404. {
  405. var array = _realm.Intrinsics.Array.ConstructFast(errors);
  406. capability.Reject.Call(Undefined, Construct(_realm.Intrinsics.AggregateError, [array]));
  407. }
  408. }
  409. // https://tc39.es/ecma262/#sec-performpromiseany
  410. try
  411. {
  412. var index = 0;
  413. do
  414. {
  415. ObjectInstance? nextItem = null;
  416. JsValue value;
  417. try
  418. {
  419. if (!iterator.TryIteratorStep(out nextItem))
  420. {
  421. doneIterating = true;
  422. RejectIfAllRejected();
  423. break;
  424. }
  425. value = nextItem.Get(CommonProperties.Value);
  426. }
  427. catch (JavaScriptException e)
  428. {
  429. if (nextItem?.Get("done")?.AsBoolean() == false)
  430. {
  431. throw;
  432. }
  433. errors.Add(e.Error);
  434. continue;
  435. }
  436. // note that null here is important
  437. // it will help to detect if all inner promises were rejected
  438. // In F# it would be Option<JsValue>
  439. errors.Add(null!);
  440. var item = promiseResolve.Call(thisObject, value);
  441. var thenProps = item.Get("then");
  442. if (thenProps is ICallable thenFunc)
  443. {
  444. var capturedIndex = index;
  445. var alreadyCalled = false;
  446. var onError =
  447. new ClrFunction(_engine, "", (_, args) =>
  448. {
  449. if (!alreadyCalled)
  450. {
  451. alreadyCalled = true;
  452. errors[capturedIndex] = args.At(0);
  453. RejectIfAllRejected();
  454. }
  455. return Undefined;
  456. }, 1, PropertyFlag.Configurable);
  457. thenFunc.Call(item, capability.ResolveObj, onError);
  458. }
  459. else
  460. {
  461. ExceptionHelper.ThrowTypeError(_realm, "Passed non Promise-like value");
  462. }
  463. index += 1;
  464. } while (true);
  465. }
  466. catch (JavaScriptException e)
  467. {
  468. iterator.Close(CompletionType.Throw);
  469. capability.Reject.Call(Undefined, e.Error);
  470. return capability.PromiseInstance;
  471. }
  472. return capability.PromiseInstance;
  473. }
  474. // https://tc39.es/ecma262/#sec-promise.race
  475. private JsValue Race(JsValue thisObject, JsCallArguments arguments)
  476. {
  477. if (!TryGetPromiseCapabilityAndIterator(thisObject, arguments, "Promise.race", out var capability, out var promiseResolve, out var iterator))
  478. return capability.PromiseInstance;
  479. // 7. Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve).
  480. // https://tc39.es/ecma262/#sec-performpromiserace
  481. try
  482. {
  483. do
  484. {
  485. JsValue nextValue;
  486. try
  487. {
  488. if (!iterator.TryIteratorStep(out var nextItem))
  489. {
  490. break;
  491. }
  492. nextValue = nextItem.Get(CommonProperties.Value);
  493. }
  494. catch (JavaScriptException e)
  495. {
  496. capability.Reject.Call(Undefined, e.Error);
  497. return capability.PromiseInstance;
  498. }
  499. // h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »).
  500. var nextPromise = promiseResolve.Call(thisObject, nextValue);
  501. // i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »).
  502. _engine.Invoke(nextPromise, "then", [(JsValue) capability.Resolve, capability.RejectObj]);
  503. } while (true);
  504. }
  505. catch (JavaScriptException e)
  506. {
  507. // 8. If result is an abrupt completion, then
  508. // a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
  509. // b. IfAbruptRejectPromise(result, promiseCapability).
  510. iterator.Close(CompletionType.Throw);
  511. capability.Reject.Call(Undefined, e.Error);
  512. return capability.PromiseInstance;
  513. }
  514. // 9. Return Completion(result).
  515. // Note that PerformPromiseRace returns a Promise instance in success case
  516. return capability.PromiseInstance;
  517. }
  518. // https://tc39.es/ecma262/#sec-getpromiseresolve
  519. // 27.2.4.1.1 GetPromiseResolve ( promiseConstructor )
  520. // The abstract operation GetPromiseResolve takes argument promiseConstructor. It performs the following steps when called:
  521. //
  522. // 1. Assert: IsConstructor(promiseConstructor) is true.
  523. // 2. Let promiseResolve be ? Get(promiseConstructor, "resolve").
  524. // 3. If IsCallable(promiseResolve) is false, throw a TypeError exception.
  525. // 4. Return promiseResolve.
  526. private ICallable GetPromiseResolve(JsValue promiseConstructor)
  527. {
  528. AssertConstructor(_engine, promiseConstructor);
  529. var resolveProp = promiseConstructor.Get("resolve");
  530. if (resolveProp is ICallable resolve)
  531. {
  532. return resolve;
  533. }
  534. ExceptionHelper.ThrowTypeError(_realm, "resolve is not a function");
  535. // Note: throws right before return
  536. return null;
  537. }
  538. // https://tc39.es/ecma262/#sec-newpromisecapability
  539. // The abstract operation NewPromiseCapability takes argument C.
  540. // It attempts to use C as a constructor in the fashion of the built-in Promise constructor to create a Promise
  541. // object and extract its resolve and reject functions.
  542. // The Promise object plus the resolve and reject functions are used to initialize a new PromiseCapability Record.
  543. // It performs the following steps when called:
  544. //
  545. // 1. If IsConstructor(C) is false, throw a TypeError exception.
  546. // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1).
  547. // 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }.
  548. // 4. Let steps be the algorithm steps defined in GetCapabilitiesExecutor Functions.
  549. // 5. Let length be the number of non-optional parameters of the function definition in GetCapabilitiesExecutor Functions.
  550. // 6. Let executor be ! CreateBuiltinFunction(steps, length, "", « [[Capability]] »).
  551. // 7. Set executor.[[Capability]] to promiseCapability.
  552. // 8. Let promise be ? Construct(C, « executor »).
  553. // 9. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
  554. // 10. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception.
  555. // 11. Set promiseCapability.[[Promise]] to promise.
  556. // 12. Return promiseCapability.
  557. internal static PromiseCapability NewPromiseCapability(Engine engine, JsValue c)
  558. {
  559. var ctor = AssertConstructor(engine, c);
  560. JsValue? resolveArg = null;
  561. JsValue? rejectArg = null;
  562. JsValue Executor(JsValue thisObject, JsCallArguments arguments)
  563. {
  564. // 25.4.1.5.1 GetCapabilitiesExecutor Functions
  565. // 3. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception.
  566. // 4. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception.
  567. // 5. Set promiseCapability.[[Resolve]] to resolve.
  568. // 6. Set promiseCapability.[[Reject]] to reject.
  569. if (resolveArg is not null && resolveArg != Undefined ||
  570. rejectArg is not null && rejectArg != Undefined)
  571. {
  572. ExceptionHelper.ThrowTypeError(engine.Realm, "executor was already called with not undefined args");
  573. }
  574. resolveArg = arguments.At(0);
  575. rejectArg = arguments.At(1);
  576. return Undefined;
  577. }
  578. var executor = new ClrFunction(engine, "", Executor, 2, PropertyFlag.Configurable);
  579. var instance = ctor.Construct([executor], c);
  580. ICallable? resolve = null;
  581. ICallable? reject = null;
  582. if (resolveArg is ICallable resFunc)
  583. {
  584. resolve = resFunc;
  585. }
  586. else
  587. {
  588. ExceptionHelper.ThrowTypeError(engine.Realm, "resolve is not a function");
  589. }
  590. if (rejectArg is ICallable rejFunc)
  591. {
  592. reject = rejFunc;
  593. }
  594. else
  595. {
  596. ExceptionHelper.ThrowTypeError(engine.Realm, "reject is not a function");
  597. }
  598. return new PromiseCapability(
  599. PromiseInstance: instance,
  600. Resolve: resolve,
  601. Reject: reject,
  602. RejectObj: rejectArg,
  603. ResolveObj: resolveArg);
  604. }
  605. }