2
0

PromiseOperations.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. using System;
  2. using System.Collections.Generic;
  3. using Jint.Native.Object;
  4. using Jint.Runtime;
  5. namespace Jint.Native.Promise
  6. {
  7. internal static class PromiseOperations
  8. {
  9. // https://tc39.es/ecma262/#sec-newpromisereactionjob
  10. //
  11. // 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called:
  12. // a. Assert: reaction is a PromiseReaction Record.
  13. // b. Let promiseCapability be reaction.[[Capability]].
  14. // c. Let type be reaction.[[Type]].
  15. // d. Let handler be reaction.[[Handler]].
  16. // e. If handler is empty, then
  17. // i. If type is Fulfill, let handlerResult be NormalCompletion(argument).
  18. // ii. Else,
  19. // 1. Assert: type is Reject.
  20. // 2. Let handlerResult be ThrowCompletion(argument).
  21. // f. Else, let handlerResult be HostCallJobCallback(handler, undefined, « argument »).
  22. // g. If promiseCapability is undefined, then
  23. // i. Assert: handlerResult is not an abrupt completion.
  24. // ii. Return NormalCompletion(empty).
  25. // h. Assert: promiseCapability is a PromiseCapability Record.
  26. // i. If handlerResult is an abrupt completion, then
  27. // i. Let status be Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).
  28. // j. Else,
  29. // i. Let status be Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »).
  30. // k. Return Completion(status).
  31. internal static Action NewPromiseReactionJob(PromiseReaction reaction, JsValue value)
  32. {
  33. return () =>
  34. {
  35. if (reaction.Handler is ICallable handler)
  36. {
  37. try
  38. {
  39. var result = handler.Call(JsValue.Undefined, new[] {value});
  40. reaction.Capability.Resolve.Call(JsValue.Undefined, new[] {result});
  41. }
  42. catch (JavaScriptException e)
  43. {
  44. reaction.Capability.Reject.Call(JsValue.Undefined, new[] {e.Error});
  45. }
  46. }
  47. else
  48. {
  49. switch (reaction.Type)
  50. {
  51. case ReactionType.Fulfill:
  52. reaction.Capability.Resolve.Call(JsValue.Undefined, new[] {value});
  53. break;
  54. case ReactionType.Reject:
  55. reaction.Capability.Reject.Call(JsValue.Undefined, new[] {value});
  56. break;
  57. default:
  58. throw new ArgumentOutOfRangeException();
  59. }
  60. }
  61. };
  62. }
  63. // https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
  64. // The abstract operation NewPromiseResolveThenableJob takes arguments promiseToResolve, thenable, and then. It performs the following steps when called:
  65. //
  66. // 1. Let job be a new Job Abstract Closure with no parameters that captures promiseToResolve,
  67. // thenable, and then and performs the following steps when called:
  68. // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve).
  69. // b. Let thenCallResult be HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »).
  70. // c. If thenCallResult is an abrupt completion, then
  71. // i. Let status be Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »).
  72. // ii. Return Completion(status).
  73. // d. Return Completion(thenCallResult).
  74. // .....Realm stuff....
  75. // 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }.
  76. internal static Action NewPromiseResolveThenableJob(PromiseInstance promise, ObjectInstance thenable,
  77. ICallable thenMethod)
  78. {
  79. return () =>
  80. {
  81. var (resolve, reject) = promise.CreateResolvingFunctions();
  82. try
  83. {
  84. thenMethod.Call(thenable, new[] {resolve as JsValue, reject});
  85. }
  86. catch (JavaScriptException e)
  87. {
  88. reject.Call(JsValue.Undefined, new[] {e.Error});
  89. }
  90. };
  91. }
  92. // https://tc39.es/ecma262/#sec-triggerpromisereactions
  93. //
  94. // 1. For each element reaction of reactions, do
  95. // a. Let job be NewPromiseReactionJob(reaction, argument).
  96. // b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
  97. // 2. Return undefined.
  98. internal static JsValue TriggerPromiseReactions(Engine engine, List<PromiseReaction> reactions, JsValue result)
  99. {
  100. foreach (var reaction in reactions)
  101. {
  102. engine.AddToEventLoop(NewPromiseReactionJob(reaction, result));
  103. }
  104. return JsValue.Undefined;
  105. }
  106. // https://tc39.es/ecma262/#sec-performpromisethen
  107. internal static JsValue PerformPromiseThen(
  108. Engine engine,
  109. PromiseInstance promise,
  110. JsValue onFulfilled,
  111. JsValue onRejected,
  112. PromiseCapability resultCapability)
  113. {
  114. var fulfilReaction = new PromiseReaction(ReactionType.Fulfill, resultCapability, onFulfilled);
  115. var rejectReaction = new PromiseReaction(ReactionType.Reject, resultCapability, onRejected);
  116. switch (promise.State)
  117. {
  118. case PromiseState.Pending:
  119. promise.PromiseFulfillReactions.Add(fulfilReaction);
  120. promise.PromiseRejectReactions.Add(rejectReaction);
  121. break;
  122. case PromiseState.Fulfilled:
  123. engine.AddToEventLoop(NewPromiseReactionJob(fulfilReaction, promise.Value));
  124. break;
  125. case PromiseState.Rejected:
  126. engine.AddToEventLoop(NewPromiseReactionJob(rejectReaction, promise.Value));
  127. break;
  128. default:
  129. ExceptionHelper.ThrowArgumentOutOfRangeException();
  130. break;
  131. }
  132. // TODO do we actually need to track that?
  133. // it seems this is mostly for debugging purposes.
  134. // E.g. to report unhandled promises to dev
  135. // 12. Set promise.[[PromiseIsHandled]] to true.
  136. return resultCapability.PromiseInstance;
  137. }
  138. }
  139. }