Skip to content

Commit

Permalink
Promise.withResolvers() returned object had resolve and reject functi…
Browse files Browse the repository at this point in the history
…ons swapped (#1983)
  • Loading branch information
tomatosalat0 authored Oct 24, 2024
1 parent bbbd8ac commit 1b0d5f8
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 43 deletions.
52 changes: 52 additions & 0 deletions Jint.Tests/Runtime/PromiseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -497,4 +497,56 @@ public void ManualPromise_HasCorrectStackTrace()

Assert.Equal("at <anonymous>:1:56", logMessage?.Trim());
}

[Fact]
public void WithResolvers_calling_resolve_resolves_promise()
{
// Arrange
using var engine = new Engine();
List<string> 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<string> expected = new() { "start", "end", "resolved" };
Assert.Equal(expected, logMessages);
}

[Fact]
public void WithResolvers_calling_reject_rejects_promise()
{
// Arrange
using var engine = new Engine();
List<string> 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<string> expected = new() { "start", "end", "rejected" };
Assert.Equal(expected, logMessages);
}
}
81 changes: 38 additions & 43 deletions Jint/Native/Promise/PromiseConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
}

/// <summary>
Expand All @@ -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;
}

/// <summary>
Expand Down Expand Up @@ -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<JsValue>();
bool doneIterating = false;

Expand All @@ -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 });
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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
{
Expand All @@ -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
Expand All @@ -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<JsValue>();
bool doneIterating = false;

Expand All @@ -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 });
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -471,8 +466,6 @@ private JsValue Any(JsValue thisObject, JsValue[] arguments)
return capability.PromiseInstance;
}

var (resultingPromise, resolve, reject, resolveObj, _) = capability;

var errors = new List<JsValue>();
var doneIterating = false;

Expand All @@ -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 }) });
}
}

Expand Down Expand Up @@ -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
{
Expand All @@ -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
Expand All @@ -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
Expand All @@ -594,16 +584,16 @@ 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 »).
var nextPromise = promiseResolve.Call(thisObject, new JsValue[] { nextValue });

// 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)
Expand All @@ -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;
}


Expand Down Expand Up @@ -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);
}
}

0 comments on commit 1b0d5f8

Please sign in to comment.