JsPromise.cs 5.0 KB

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