PromiseConstructor.cs 29 KB

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