using Jint.Native.Object; using Jint.Runtime; namespace Jint.Native.Promise; internal static class PromiseOperations { // https://tc39.es/ecma262/#sec-newpromisereactionjob // // 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called: // a. Assert: reaction is a PromiseReaction Record. // b. Let promiseCapability be reaction.[[Capability]]. // c. Let type be reaction.[[Type]]. // d. Let handler be reaction.[[Handler]]. // e. If handler is empty, then // i. If type is Fulfill, let handlerResult be NormalCompletion(argument). // ii. Else, // 1. Assert: type is Reject. // 2. Let handlerResult be ThrowCompletion(argument). // f. Else, let handlerResult be HostCallJobCallback(handler, undefined, « argument »). // g. If promiseCapability is undefined, then // i. Assert: handlerResult is not an abrupt completion. // ii. Return NormalCompletion(empty). // h. Assert: promiseCapability is a PromiseCapability Record. // i. If handlerResult is an abrupt completion, then // i. Let status be Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). // j. Else, // i. Let status be Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). // k. Return Completion(status). private static Action NewPromiseReactionJob(PromiseReaction reaction, JsValue value) { return () => { var promiseCapability = reaction.Capability; if (reaction.Handler is ICallable handler) { try { var result = handler.Call(JsValue.Undefined, value); promiseCapability.Resolve.Call(JsValue.Undefined, result); } catch (JavaScriptException e) { promiseCapability.Reject.Call(JsValue.Undefined, e.Error); } } else { switch (reaction.Type) { case ReactionType.Fulfill: promiseCapability.Resolve.Call(JsValue.Undefined, value); break; case ReactionType.Reject: promiseCapability.Reject.Call(JsValue.Undefined, value); break; default: throw new ArgumentOutOfRangeException(nameof(reaction), "Unknown reaction type"); } } }; } // https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob // The abstract operation NewPromiseResolveThenableJob takes arguments promiseToResolve, thenable, and then. It performs the following steps when called: // // 1. Let job be a new Job Abstract Closure with no parameters that captures promiseToResolve, // thenable, and then and performs the following steps when called: // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve). // b. Let thenCallResult be HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »). // c. If thenCallResult is an abrupt completion, then // i. Let status be Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). // ii. Return Completion(status). // d. Return Completion(thenCallResult). // .....Realm stuff.... // 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }. internal static Action NewPromiseResolveThenableJob(JsPromise promise, ObjectInstance thenable, ICallable thenMethod) { return () => { var (resolve, reject) = promise.CreateResolvingFunctions(); try { thenMethod.Call(thenable, resolve as JsValue, reject); } catch (JavaScriptException e) { reject.Call(JsValue.Undefined, [e.Error]); } }; } // https://tc39.es/ecma262/#sec-triggerpromisereactions // // 1. For each element reaction of reactions, do // a. Let job be NewPromiseReactionJob(reaction, argument). // b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). // 2. Return undefined. internal static JsValue TriggerPromiseReactions(Engine engine, List reactions, JsValue result) { foreach (var reaction in reactions) { engine.AddToEventLoop(NewPromiseReactionJob(reaction, result)); } return JsValue.Undefined; } // https://tc39.es/ecma262/#sec-performpromisethen internal static JsValue PerformPromiseThen( Engine engine, JsPromise promise, JsValue onFulfilled, JsValue onRejected, PromiseCapability resultCapability) { var fulfilReaction = new PromiseReaction(ReactionType.Fulfill, resultCapability, onFulfilled); var rejectReaction = new PromiseReaction(ReactionType.Reject, resultCapability, onRejected); switch (promise.State) { case PromiseState.Pending: promise.PromiseFulfillReactions.Add(fulfilReaction); promise.PromiseRejectReactions.Add(rejectReaction); break; case PromiseState.Fulfilled: engine.AddToEventLoop(NewPromiseReactionJob(fulfilReaction, promise.Value)); break; case PromiseState.Rejected: engine.AddToEventLoop(NewPromiseReactionJob(rejectReaction, promise.Value)); break; default: Throw.ArgumentOutOfRangeException(); break; } //https://tc39.es/ecma262/#sec-performpromisethen //... //13. If resultCapability is undefined, then // a. Return undefined //14. Else // a. Return resultCapability.[[Promise]] if (resultCapability is null) { return JsValue.Undefined; } return resultCapability.PromiseInstance; } }