123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- using System.Threading;
- using Jint.Native.Object;
- using Jint.Native.Promise;
- using Jint.Runtime;
- using Jint.Runtime.Descriptors;
- using Jint.Runtime.Interop;
- namespace Jint.Native;
- internal sealed class JsPromise : ObjectInstance
- {
- internal PromiseState State { get; private set; }
- // valid only in settled state (Fulfilled or Rejected)
- internal JsValue Value { get; private set; } = null!;
- internal ManualResetEventSlim CompletedEvent { get; } = new();
- internal List<PromiseReaction> PromiseRejectReactions = new();
- internal List<PromiseReaction> PromiseFulfillReactions = new();
- internal JsPromise(Engine engine) : base(engine)
- {
- }
- // https://tc39.es/ecma262/#sec-createresolvingfunctions
- // Note that functions capture over alreadyResolved
- // that does imply that the same promise can be resolved twice but with different resolving functions
- internal ResolvingFunctions CreateResolvingFunctions()
- {
- var alreadyResolved = false;
- var resolve = new ClrFunction(_engine, "", (thisObj, args) =>
- {
- if (alreadyResolved)
- {
- return Undefined;
- }
- alreadyResolved = true;
- return Resolve(thisObj, args);
- }, 1, PropertyFlag.Configurable);
- var reject = new ClrFunction(_engine, "", (thisObj, args) =>
- {
- if (alreadyResolved)
- {
- return Undefined;
- }
- alreadyResolved = true;
- return Reject(thisObj, args);
- }, 1, PropertyFlag.Configurable);
- return new ResolvingFunctions(resolve, reject);
- }
- // https://tc39.es/ecma262/#sec-promise-resolve-functions
- private JsValue Resolve(JsValue thisObject, JsCallArguments arguments)
- {
- var result = arguments.At(0);
- return Resolve(result);
- }
- internal JsValue Resolve(JsValue result)
- {
- // Note that alreadyResolved logic lives in CreateResolvingFunctions method
- if (ReferenceEquals(result, this))
- {
- return RejectPromise(_engine.Realm.Intrinsics.TypeError.Construct("Cannot resolve Promise with itself"));
- }
- if (result is not ObjectInstance resultObj)
- {
- return FulfillPromise(result);
- }
- JsValue thenProp;
- try
- {
- thenProp = resultObj.Get("then");
- }
- catch (JavaScriptException e)
- {
- return RejectPromise(e.Error);
- }
- if (thenProp is not ICallable thenMethod)
- {
- return FulfillPromise(result);
- }
- var realm = _engine.Realm;
- var job = PromiseOperations.NewPromiseResolveThenableJob(this, resultObj, thenMethod);
- _engine._host.HostEnqueuePromiseJob(job, realm);
- return Undefined;
- }
- // https://tc39.es/ecma262/#sec-promise-reject-functions
- private JsValue Reject(JsValue thisObject, JsCallArguments arguments)
- {
- // Note that alreadyResolved logic lives in CreateResolvingFunctions method
- var reason = arguments.At(0);
- return RejectPromise(reason);
- }
- // https://tc39.es/ecma262/#sec-rejectpromise
- // 1. Assert: The value of promise.[[PromiseState]] is pending.
- // 2. Let reactions be promise.[[PromiseRejectReactions]].
- // 3. Set promise.[[PromiseResult]] to reason.
- // 4. Set promise.[[PromiseFulfillReactions]] to undefined.
- // 5. Set promise.[[PromiseRejectReactions]] to undefined.
- // 6. Set promise.[[PromiseState]] to rejected.
- // 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject").
- // 8. Return TriggerPromiseReactions(reactions, reason).
- private JsValue RejectPromise(JsValue reason)
- {
- if (State != PromiseState.Pending)
- {
- Throw.InvalidOperationException("Promise should be in Pending state");
- }
- Settle(PromiseState.Rejected, reason);
- var reactions = PromiseRejectReactions;
- PromiseRejectReactions = new List<PromiseReaction>();
- PromiseFulfillReactions.Clear();
- CompletedEvent.Set();
- // Note that this part is skipped because there is no tracking yet
- // 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject").
- return PromiseOperations.TriggerPromiseReactions(_engine, reactions, reason);
- }
- // https://tc39.es/ecma262/#sec-fulfillpromise
- private JsValue FulfillPromise(JsValue result)
- {
- if (State != PromiseState.Pending)
- {
- Throw.InvalidOperationException("Promise should be in Pending state");
- }
- Settle(PromiseState.Fulfilled, result);
- var reactions = PromiseFulfillReactions;
- PromiseFulfillReactions = new List<PromiseReaction>();
- PromiseRejectReactions.Clear();
- CompletedEvent.Set();
- return PromiseOperations.TriggerPromiseReactions(_engine, reactions, result);
- }
- private void Settle(PromiseState state, JsValue result)
- {
- State = state;
- Value = result;
- }
- }
|