JsPromise.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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 JsPromise : 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 JsPromise(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 ClrFunction(_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 ClrFunction(_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 thisObject, JsValue[] arguments)
  70. {
  71. var result = arguments.At(0);
  72. return Resolve(result);
  73. }
  74. internal JsValue Resolve(JsValue result)
  75. {
  76. // Note that alreadyResolved logic lives in CreateResolvingFunctions method
  77. if (ReferenceEquals(result, this))
  78. {
  79. return RejectPromise(_engine.Realm.Intrinsics.TypeError.Construct("Cannot resolve Promise with itself"));
  80. }
  81. if (result is not ObjectInstance resultObj)
  82. {
  83. return FulfillPromise(result);
  84. }
  85. JsValue thenProp;
  86. try
  87. {
  88. thenProp = resultObj.Get("then");
  89. }
  90. catch (JavaScriptException e)
  91. {
  92. return RejectPromise(e.Error);
  93. }
  94. if (thenProp is not ICallable thenMethod)
  95. {
  96. return FulfillPromise(result);
  97. }
  98. var realm = _engine.Realm;
  99. var job = PromiseOperations.NewPromiseResolveThenableJob(this, resultObj, thenMethod);
  100. _engine._host.HostEnqueuePromiseJob(job, realm);
  101. return Undefined;
  102. }
  103. // https://tc39.es/ecma262/#sec-promise-reject-functions
  104. private JsValue Reject(JsValue thisObject, JsValue[] arguments)
  105. {
  106. // Note that alreadyResolved logic lives in CreateResolvingFunctions method
  107. var reason = arguments.At(0);
  108. return RejectPromise(reason);
  109. }
  110. // https://tc39.es/ecma262/#sec-rejectpromise
  111. // 1. Assert: The value of promise.[[PromiseState]] is pending.
  112. // 2. Let reactions be promise.[[PromiseRejectReactions]].
  113. // 3. Set promise.[[PromiseResult]] to reason.
  114. // 4. Set promise.[[PromiseFulfillReactions]] to undefined.
  115. // 5. Set promise.[[PromiseRejectReactions]] to undefined.
  116. // 6. Set promise.[[PromiseState]] to rejected.
  117. // 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject").
  118. // 8. Return TriggerPromiseReactions(reactions, reason).
  119. private JsValue RejectPromise(JsValue reason)
  120. {
  121. if (State != PromiseState.Pending)
  122. {
  123. ExceptionHelper.ThrowInvalidOperationException("Promise should be in Pending state");
  124. }
  125. Settle(PromiseState.Rejected, reason);
  126. var reactions = PromiseRejectReactions;
  127. PromiseRejectReactions = new List<PromiseReaction>();
  128. PromiseFulfillReactions.Clear();
  129. // Note that this part is skipped because there is no tracking yet
  130. // 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject").
  131. return PromiseOperations.TriggerPromiseReactions(_engine, reactions, reason);
  132. }
  133. // https://tc39.es/ecma262/#sec-fulfillpromise
  134. private JsValue FulfillPromise(JsValue result)
  135. {
  136. if (State != PromiseState.Pending)
  137. {
  138. ExceptionHelper.ThrowInvalidOperationException("Promise should be in Pending state");
  139. }
  140. Settle(PromiseState.Fulfilled, result);
  141. var reactions = PromiseFulfillReactions;
  142. PromiseFulfillReactions = new List<PromiseReaction>();
  143. PromiseRejectReactions.Clear();
  144. return PromiseOperations.TriggerPromiseReactions(_engine, reactions, result);
  145. }
  146. private void Settle(PromiseState state, JsValue result)
  147. {
  148. State = state;
  149. Value = result;
  150. }
  151. }