JsPromise.cs 4.9 KB

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