diff --git a/Jint.Tests/Runtime/PromiseTests.cs b/Jint.Tests/Runtime/PromiseTests.cs index a4e86f325..e791153d7 100644 --- a/Jint.Tests/Runtime/PromiseTests.cs +++ b/Jint.Tests/Runtime/PromiseTests.cs @@ -497,4 +497,56 @@ public void ManualPromise_HasCorrectStackTrace() Assert.Equal("at :1:56", logMessage?.Trim()); } + + [Fact] + public void WithResolvers_calling_resolve_resolves_promise() + { + // Arrange + using var engine = new Engine(); + List logMessages = new(); + engine.SetValue("log", logMessages.Add); + + // Act + engine.Execute(""" + const p = Promise.withResolvers(); + const next = p.promise + .then(() => log('resolved')) + .catch(() => log('rejected')); + + log('start'); + p.resolve(); + log('end'); + """); + engine.RunAvailableContinuations(); + + // Assert + List expected = new() { "start", "end", "resolved" }; + Assert.Equal(expected, logMessages); + } + + [Fact] + public void WithResolvers_calling_reject_rejects_promise() + { + // Arrange + using var engine = new Engine(); + List logMessages = new(); + engine.SetValue("log", logMessages.Add); + + // Act + engine.Execute(""" + const p = Promise.withResolvers(); + const next = p.promise + .then(() => log('resolved')) + .catch(() => log('rejected')); + + log('start'); + p.reject(); + log('end'); + """); + engine.RunAvailableContinuations(); + + // Assert + List expected = new() { "start", "end", "rejected" }; + Assert.Equal(expected, logMessages); + } } diff --git a/Jint/Native/Promise/PromiseConstructor.cs b/Jint/Native/Promise/PromiseConstructor.cs index 0f3d847b8..2fcc124b7 100644 --- a/Jint/Native/Promise/PromiseConstructor.cs +++ b/Jint/Native/Promise/PromiseConstructor.cs @@ -13,9 +13,8 @@ internal sealed record PromiseCapability( JsValue PromiseInstance, ICallable Resolve, ICallable Reject, - JsValue RejectObj, - JsValue ResolveObj -); + JsValue ResolveObj, + JsValue RejectObj); internal sealed class PromiseConstructor : Constructor { @@ -139,11 +138,11 @@ private JsValue PromiseResolve(JsValue thisObject, JsValue x) } } - var (instance, resolve, _, _, _) = NewPromiseCapability(_engine, thisObject); + var capability = NewPromiseCapability(_engine, thisObject); - resolve.Call(Undefined, new[] { x }); + capability.Resolve.Call(Undefined, new[] { x }); - return instance; + return capability.PromiseInstance; } /// @@ -163,11 +162,11 @@ private JsValue Reject(JsValue thisObject, JsValue[] arguments) var r = arguments.At(0); - var (instance, _, reject, _, _) = NewPromiseCapability(_engine, thisObject); + var capability = NewPromiseCapability(_engine, thisObject); - reject.Call(Undefined, new[] { r }); + capability.Reject.Call(Undefined, new[] { r }); - return instance; + return capability.PromiseInstance; } /// @@ -255,8 +254,6 @@ private JsValue All(JsValue thisObject, JsValue[] arguments) if (!TryGetPromiseCapabilityAndIterator(thisObject, arguments, "Promise.all", out var capability, out var promiseResolve, out var iterator)) return capability.PromiseInstance; - var (resultingPromise, resolve, reject, _, rejectObj) = capability; - var results = new List(); bool doneIterating = false; @@ -269,7 +266,7 @@ void ResolveIfFinished() if (results.TrueForAll(static x => x is not null) && doneIterating) { var array = _realm.Intrinsics.Array.ConstructFast(results); - resolve.Call(Undefined, new JsValue[] { array }); + capability.Resolve.Call(Undefined, new JsValue[] { array }); } } @@ -296,8 +293,8 @@ void ResolveIfFinished() } catch (JavaScriptException e) { - reject.Call(Undefined, new[] { e.Error }); - return resultingPromise; + capability.Reject.Call(Undefined, new[] { e.Error }); + return capability.PromiseInstance; } // note that null here is important @@ -325,7 +322,7 @@ void ResolveIfFinished() return Undefined; }, 1, PropertyFlag.Configurable); - thenFunc.Call(item, new JsValue[] { onSuccess, rejectObj }); + thenFunc.Call(item, new JsValue[] { onSuccess, capability.RejectObj }); } else { @@ -338,11 +335,11 @@ void ResolveIfFinished() catch (JavaScriptException e) { iterator.Close(CompletionType.Throw); - reject.Call(Undefined, new[] { e.Error }); - return resultingPromise; + capability.Reject.Call(Undefined, new[] { e.Error }); + return capability.PromiseInstance; } - return resultingPromise; + return capability.PromiseInstance; } // https://tc39.es/ecma262/#sec-promise.allsettled @@ -351,8 +348,6 @@ private JsValue AllSettled(JsValue thisObject, JsValue[] arguments) if (!TryGetPromiseCapabilityAndIterator(thisObject, arguments, "Promise.allSettled", out var capability, out var promiseResolve, out var iterator)) return capability.PromiseInstance; - var (resultingPromise, resolve, reject, _, rejectObj) = capability; - var results = new List(); bool doneIterating = false; @@ -365,7 +360,7 @@ void ResolveIfFinished() if (results.TrueForAll(static x => x is not null) && doneIterating) { var array = _realm.Intrinsics.Array.ConstructFast(results); - resolve.Call(Undefined, new JsValue[] { array }); + capability.Resolve.Call(Undefined, new JsValue[] { array }); } } @@ -392,8 +387,8 @@ void ResolveIfFinished() } catch (JavaScriptException e) { - reject.Call(Undefined, new[] { e.Error }); - return resultingPromise; + capability.Reject.Call(Undefined, new[] { e.Error }); + return capability.PromiseInstance; } // note that null here is important @@ -456,11 +451,11 @@ void ResolveIfFinished() catch (JavaScriptException e) { iterator.Close(CompletionType.Throw); - reject.Call(Undefined, new[] { e.Error }); - return resultingPromise; + capability.Reject.Call(Undefined, new[] { e.Error }); + return capability.PromiseInstance; } - return resultingPromise; + return capability.PromiseInstance; } // https://tc39.es/ecma262/#sec-promise.any @@ -471,8 +466,6 @@ private JsValue Any(JsValue thisObject, JsValue[] arguments) return capability.PromiseInstance; } - var (resultingPromise, resolve, reject, resolveObj, _) = capability; - var errors = new List(); var doneIterating = false; @@ -487,7 +480,7 @@ void RejectIfAllRejected() { var array = _realm.Intrinsics.Array.ConstructFast(errors); - reject.Call(Undefined, new JsValue[] { Construct(_realm.Intrinsics.AggregateError, new JsValue[] { array }) }); + capability.Reject.Call(Undefined, new JsValue[] { Construct(_realm.Intrinsics.AggregateError, new JsValue[] { array }) }); } } @@ -547,7 +540,7 @@ void RejectIfAllRejected() return Undefined; }, 1, PropertyFlag.Configurable); - thenFunc.Call(item, new JsValue[] { resolveObj, onError }); + thenFunc.Call(item, new JsValue[] { capability.ResolveObj, onError }); } else { @@ -560,11 +553,11 @@ void RejectIfAllRejected() catch (JavaScriptException e) { iterator.Close(CompletionType.Throw); - reject.Call(Undefined, new[] { e.Error }); - return resultingPromise; + capability.Reject.Call(Undefined, new[] { e.Error }); + return capability.PromiseInstance; } - return resultingPromise; + return capability.PromiseInstance; } // https://tc39.es/ecma262/#sec-promise.race @@ -573,9 +566,6 @@ private JsValue Race(JsValue thisObject, JsValue[] arguments) if (!TryGetPromiseCapabilityAndIterator(thisObject, arguments, "Promise.race", out var capability, out var promiseResolve, out var iterator)) return capability.PromiseInstance; - var (resultingPromise, resolve, reject, _, rejectObj) = capability; - - // 7. Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve). // https://tc39.es/ecma262/#sec-performpromiserace try @@ -594,8 +584,8 @@ private JsValue Race(JsValue thisObject, JsValue[] arguments) } catch (JavaScriptException e) { - reject.Call(Undefined, new[] { e.Error }); - return resultingPromise; + capability.Reject.Call(Undefined, new[] { e.Error }); + return capability.PromiseInstance; } // h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). @@ -603,7 +593,7 @@ private JsValue Race(JsValue thisObject, JsValue[] arguments) // i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »). - _engine.Invoke(nextPromise, "then", new[] { (JsValue) resolve, rejectObj }); + _engine.Invoke(nextPromise, "then", new[] { (JsValue) capability.Resolve, capability.RejectObj }); } while (true); } catch (JavaScriptException e) @@ -612,13 +602,13 @@ private JsValue Race(JsValue thisObject, JsValue[] arguments) // a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result). // b. IfAbruptRejectPromise(result, promiseCapability). iterator.Close(CompletionType.Throw); - reject.Call(Undefined, new[] { e.Error }); - return resultingPromise; + capability.Reject.Call(Undefined, new[] { e.Error }); + return capability.PromiseInstance; } // 9. Return Completion(result). // Note that PerformPromiseRace returns a Promise instance in success case - return resultingPromise; + return capability.PromiseInstance; } @@ -715,6 +705,11 @@ JsValue Executor(JsValue thisObject, JsValue[] arguments) ExceptionHelper.ThrowTypeError(engine.Realm, "reject is not a function"); } - return new PromiseCapability(instance, resolve, reject, resolveArg, rejectArg); + return new PromiseCapability( + PromiseInstance: instance, + Resolve: resolve, + Reject: reject, + RejectObj: rejectArg, + ResolveObj: resolveArg); } }