2
0

PromiseInstance.cs 5.9 KB

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