PromiseConstructor.cs 27 KB

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