PromiseInstance.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. using Jint.Native.Function;
  2. using Jint.Native.Object;
  3. using Jint.Runtime;
  4. using Jint.Runtime.Descriptors;
  5. using Jint.Runtime.Interop;
  6. namespace Jint.Native.Promise;
  7. internal enum PromiseState
  8. {
  9. Pending,
  10. Fulfilled,
  11. Rejected
  12. }
  13. internal enum ReactionType
  14. {
  15. Fulfill,
  16. Reject
  17. }
  18. internal sealed record PromiseReaction(
  19. ReactionType Type,
  20. PromiseCapability Capability,
  21. JsValue Handler
  22. );
  23. internal sealed record ResolvingFunctions(
  24. FunctionInstance Resolve,
  25. FunctionInstance Reject
  26. );
  27. public sealed record ManualPromise(
  28. JsValue Promise,
  29. Action<JsValue> Resolve,
  30. Action<JsValue> Reject
  31. );
  32. internal sealed class PromiseInstance : ObjectInstance
  33. {
  34. internal PromiseState State { get; private set; }
  35. // valid only in settled state (Fulfilled or Rejected)
  36. internal JsValue Value { get; private set; } = null!;
  37. internal List<PromiseReaction> PromiseRejectReactions = new();
  38. internal List<PromiseReaction> PromiseFulfillReactions = new();
  39. internal PromiseInstance(Engine engine) : base(engine)
  40. {
  41. }
  42. // https://tc39.es/ecma262/#sec-createresolvingfunctions
  43. // Note that functions capture over alreadyResolved
  44. // that does imply that the same promise can be resolved twice but with different resolving functions
  45. internal ResolvingFunctions CreateResolvingFunctions()
  46. {
  47. var alreadyResolved = false;
  48. var resolve = new ClrFunctionInstance(_engine, "", (thisObj, args) =>
  49. {
  50. if (alreadyResolved)
  51. {
  52. return Undefined;
  53. }
  54. alreadyResolved = true;
  55. return Resolve(thisObj, args);
  56. }, 1, PropertyFlag.Configurable);
  57. var reject = new ClrFunctionInstance(_engine, "", (thisObj, args) =>
  58. {
  59. if (alreadyResolved)
  60. {
  61. return Undefined;
  62. }
  63. alreadyResolved = true;
  64. return Reject(thisObj, args);
  65. }, 1, PropertyFlag.Configurable);
  66. return new ResolvingFunctions(resolve, reject);
  67. }
  68. // https://tc39.es/ecma262/#sec-promise-resolve-functions
  69. private JsValue Resolve(JsValue thisObj, JsValue[] arguments)
  70. {
  71. // Note that alreadyResolved logic lives in CreateResolvingFunctions method
  72. var result = arguments.At(0);
  73. if (ReferenceEquals(result, this))
  74. {
  75. return RejectPromise(_engine.Realm.Intrinsics.TypeError.Construct("Cannot resolve Promise with itself"));
  76. }
  77. if (result is not ObjectInstance resultObj)
  78. {
  79. return FulfillPromise(result);
  80. }
  81. JsValue thenProp;
  82. try
  83. {
  84. thenProp = resultObj.Get("then");
  85. }
  86. catch (JavaScriptException e)
  87. {
  88. return RejectPromise(e.Error);
  89. }
  90. if (thenProp is not ICallable thenMethod)
  91. {
  92. return FulfillPromise(result);
  93. }
  94. _engine.AddToEventLoop(
  95. PromiseOperations.NewPromiseResolveThenableJob(this, resultObj, thenMethod));
  96. return Undefined;
  97. }
  98. // https://tc39.es/ecma262/#sec-promise-reject-functions
  99. private JsValue Reject(JsValue thisObj, JsValue[] arguments)
  100. {
  101. // Note that alreadyResolved logic lives in CreateResolvingFunctions method
  102. var reason = arguments.At(0);
  103. return RejectPromise(reason);
  104. }
  105. // https://tc39.es/ecma262/#sec-rejectpromise
  106. // 1. Assert: The value of promise.[[PromiseState]] is pending.
  107. // 2. Let reactions be promise.[[PromiseRejectReactions]].
  108. // 3. Set promise.[[PromiseResult]] to reason.
  109. // 4. Set promise.[[PromiseFulfillReactions]] to undefined.
  110. // 5. Set promise.[[PromiseRejectReactions]] to undefined.
  111. // 6. Set promise.[[PromiseState]] to rejected.
  112. // 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject").
  113. // 8. Return TriggerPromiseReactions(reactions, reason).
  114. private JsValue RejectPromise(JsValue reason)
  115. {
  116. if (State != PromiseState.Pending)
  117. {
  118. ExceptionHelper.ThrowInvalidOperationException("Promise should be in Pending state");
  119. }
  120. Settle(PromiseState.Rejected, reason);
  121. var reactions = PromiseRejectReactions;
  122. PromiseRejectReactions = new List<PromiseReaction>();
  123. PromiseFulfillReactions.Clear();
  124. // Note that this part is skipped because there is no tracking yet
  125. // 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject").
  126. return PromiseOperations.TriggerPromiseReactions(_engine, reactions, reason);
  127. }
  128. // https://tc39.es/ecma262/#sec-fulfillpromise
  129. private JsValue FulfillPromise(JsValue result)
  130. {
  131. if (State != PromiseState.Pending)
  132. {
  133. ExceptionHelper.ThrowInvalidOperationException("Promise should be in Pending state");
  134. }
  135. Settle(PromiseState.Fulfilled, result);
  136. var reactions = PromiseFulfillReactions;
  137. PromiseFulfillReactions = new List<PromiseReaction>();
  138. PromiseRejectReactions.Clear();
  139. return PromiseOperations.TriggerPromiseReactions(_engine, reactions, result);
  140. }
  141. private void Settle(PromiseState state, JsValue result)
  142. {
  143. State = state;
  144. Value = result;
  145. }
  146. }