JsPromise.cs 5.1 KB

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