mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-20 22:27:31 +09:00
Issue #2089 - Optimize Promise.then and Promise.all/race with default resolving functions
Based-on: m-c 1401508,1412200,1473970/{1-4}
This commit is contained in:
parent
55eacd43a4
commit
9744b0c2bc
17 changed files with 906 additions and 142 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Promise.race(...) may add a dummy PromiseReaction which is only used for the
|
||||||
|
// debugger.
|
||||||
|
//
|
||||||
|
// See BlockOnPromise when called from PerformPromiseRace for when this dummy
|
||||||
|
// reaction is created.
|
||||||
|
|
||||||
|
var g = newGlobal();
|
||||||
|
var dbg = new Debugger();
|
||||||
|
var gw = dbg.addDebuggee(g);
|
||||||
|
|
||||||
|
function test(withFastPath) {
|
||||||
|
g.eval(`
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
var {promise: alwaysPending} = newPromiseCapability();
|
||||||
|
|
||||||
|
if (!${withFastPath}) {
|
||||||
|
// Disable the BlockOnPromise fast path by giving |alwaysPending| a
|
||||||
|
// non-default "then" function property. This will ensure the dummy
|
||||||
|
// reaction is created.
|
||||||
|
alwaysPending.then = function() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = Promise.race([alwaysPending]);
|
||||||
|
`);
|
||||||
|
|
||||||
|
var alwaysPending = gw.makeDebuggeeValue(g.alwaysPending);
|
||||||
|
var result = gw.makeDebuggeeValue(g.result);
|
||||||
|
|
||||||
|
assertEq(alwaysPending.promiseDependentPromises.length, 1);
|
||||||
|
assertEq(alwaysPending.promiseDependentPromises[0], result);
|
||||||
|
|
||||||
|
assertEq(result.promiseDependentPromises.length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No dummy reaction created when the fast path is taken.
|
||||||
|
test(true);
|
||||||
|
|
||||||
|
// Dummy reaction is created when we can't take the fast path.
|
||||||
|
test(false);
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Promise.race(...) may add a dummy PromiseReaction which is only used for the
|
||||||
|
// debugger. Ensure that this dummy reaction can't influence the normal Promise
|
||||||
|
// resolution behaviour.
|
||||||
|
//
|
||||||
|
// See BlockOnPromise when called from PerformPromiseRace for when this dummy
|
||||||
|
// reaction is created.
|
||||||
|
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
function neverCalled() {
|
||||||
|
// Quit with non-zero exit code to ensure a test suite error is shown,
|
||||||
|
// even when this function is called within promise handlers which normally
|
||||||
|
// swallow any exceptions.
|
||||||
|
quit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = 0;
|
||||||
|
var g_resolve;
|
||||||
|
|
||||||
|
var resolvedValues = [];
|
||||||
|
|
||||||
|
function resolveCapability(v) {
|
||||||
|
resolvedValues.push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
class P extends Promise {
|
||||||
|
constructor(executor) {
|
||||||
|
// Only the very first object created through this constructor gets
|
||||||
|
// special treatment, all other invocations create built-in Promise
|
||||||
|
// objects.
|
||||||
|
if (c++ > 1) {
|
||||||
|
return new Promise(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
executor(resolveCapability, neverCalled);
|
||||||
|
|
||||||
|
var {promise, resolve} = newPromiseCapability();
|
||||||
|
g_resolve = resolve;
|
||||||
|
|
||||||
|
// Use an async function to create a Promise without resolving functions.
|
||||||
|
var p = async function(){ await promise; return 456; }();
|
||||||
|
|
||||||
|
// Ensure the species constructor is not the built-in Promise constructor
|
||||||
|
// to avoid falling into the fast path.
|
||||||
|
p.constructor = {
|
||||||
|
[Symbol.species]: P
|
||||||
|
};
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var {promise: alwaysPending} = newPromiseCapability();
|
||||||
|
|
||||||
|
// The promise returned from race() should never be resolved.
|
||||||
|
P.race([alwaysPending]).then(neverCalled, neverCalled);
|
||||||
|
|
||||||
|
g_resolve(123);
|
||||||
|
|
||||||
|
drainJobQueue();
|
||||||
|
|
||||||
|
// Check |resolvedValues| to ensure resolving functions were properly called.
|
||||||
|
assertEq(resolvedValues.length, 2);
|
||||||
|
assertEq(resolvedValues[0], alwaysPending);
|
||||||
|
assertEq(resolvedValues[1], 456);
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var {promise, resolve} = newPromiseCapability();
|
||||||
|
|
||||||
|
resolve(Promise.resolve(0));
|
||||||
|
|
||||||
|
// Don't assert when the Promise was already resolved.
|
||||||
|
resolvePromise(promise, 123);
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Test we don't assert when the promise is settled after enqueuing a PromiseReactionJob.
|
||||||
|
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var {promise, resolve} = newPromiseCapability();
|
||||||
|
|
||||||
|
var p = Promise.resolve(0);
|
||||||
|
|
||||||
|
// Enqueue a PromiseResolveThenableJob followed by a PromiseReactionJob.
|
||||||
|
resolve(p);
|
||||||
|
|
||||||
|
// The PromiseReactionJob expects a pending promise, but this settlePromiseNow
|
||||||
|
// call will already have settled the promise.
|
||||||
|
settlePromiseNow(promise);
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Don't assert when the promise in the resolving functions is wrapped in a CCW.
|
||||||
|
|
||||||
|
function newPromiseCapability(newTarget) {
|
||||||
|
var resolve, reject, promise = Reflect.construct(Promise, [function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
}], newTarget);
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
var g = newGlobal();
|
||||||
|
|
||||||
|
var {promise, resolve} = newPromiseCapability(g.Promise);
|
||||||
|
|
||||||
|
g.settlePromiseNow(promise);
|
||||||
|
|
||||||
|
// Don't assert when resolving the promise.
|
||||||
|
resolve(0);
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Don't assert when the promise in the resolving functions is wrapped in a CCW.
|
||||||
|
|
||||||
|
function newPromiseCapability(newTarget) {
|
||||||
|
var resolve, reject, promise = Reflect.construct(Promise, [function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
}], newTarget);
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
var g = newGlobal();
|
||||||
|
|
||||||
|
var {promise, reject} = newPromiseCapability(g.Promise);
|
||||||
|
|
||||||
|
g.settlePromiseNow(promise);
|
||||||
|
|
||||||
|
// Don't assert when rejecting the promise.
|
||||||
|
reject(0);
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Test we don't assert when the promise is settled and the SpeciesConstructor
|
||||||
|
// call in Promise.prototype.then throws an exception.
|
||||||
|
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var {promise, resolve} = newPromiseCapability();
|
||||||
|
|
||||||
|
var p = Promise.resolve(0);
|
||||||
|
|
||||||
|
p.constructor = {
|
||||||
|
[Symbol.species]: function() {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enqueue a PromiseResolveThenableJob.
|
||||||
|
resolve(p);
|
||||||
|
|
||||||
|
// Settle the promise after the resolve call.
|
||||||
|
settlePromiseNow(promise);
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Test we don't assert when the promise is settled and the SpeciesConstructor
|
||||||
|
// call in Promise.prototype.then throws an exception.
|
||||||
|
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var {promise, resolve} = newPromiseCapability();
|
||||||
|
|
||||||
|
var p = Promise.resolve(0);
|
||||||
|
|
||||||
|
p.constructor = {
|
||||||
|
[Symbol.species]: function() {
|
||||||
|
// Settle the promise in the SpeciesConstructor call.
|
||||||
|
settlePromiseNow(promise);
|
||||||
|
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enqueue a PromiseResolveThenableJob.
|
||||||
|
resolve(p);
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Test we don't assert when the promise is settled and we then try to call the
|
||||||
|
// resolving function.
|
||||||
|
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var {promise, resolve} = newPromiseCapability();
|
||||||
|
|
||||||
|
settlePromiseNow(promise);
|
||||||
|
|
||||||
|
// Don't assert when the promise is already settled.
|
||||||
|
resolve(0);
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Test we don't assert when the promise is settled and we then try to call the
|
||||||
|
// rejecting function.
|
||||||
|
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var {promise, reject} = newPromiseCapability();
|
||||||
|
|
||||||
|
settlePromiseNow(promise);
|
||||||
|
|
||||||
|
// Don't assert when the promise is already settled.
|
||||||
|
reject(0);
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Don't assert when a side-effect when getting the "then" property settled the promise.
|
||||||
|
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var {promise, resolve} = newPromiseCapability();
|
||||||
|
|
||||||
|
var thenable = {
|
||||||
|
get then() {
|
||||||
|
settlePromiseNow(promise);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
resolve(thenable);
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Don't assert when a side-effect when getting the "then" property settled the promise.
|
||||||
|
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var {promise, resolve} = newPromiseCapability();
|
||||||
|
|
||||||
|
var thenable = {
|
||||||
|
get then() {
|
||||||
|
settlePromiseNow(promise);
|
||||||
|
|
||||||
|
// Throw an error to reject the promise.
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
resolve(thenable);
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Don't assert when settlePromiseNow() is called on an async-function promise.
|
||||||
|
|
||||||
|
var promise = async function(){ await 0; }();
|
||||||
|
|
||||||
|
try {
|
||||||
|
settlePromiseNow(promise);
|
||||||
|
} catch {}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
function newPromiseCapability() {
|
||||||
|
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||||
|
resolve = r1;
|
||||||
|
reject = r2;
|
||||||
|
});
|
||||||
|
return {promise, resolve, reject};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var {promise, resolve, reject} = newPromiseCapability();
|
||||||
|
|
||||||
|
settlePromiseNow(promise);
|
||||||
|
|
||||||
|
assertEq(resolve(0), undefined);
|
||||||
|
assertEq(reject(0), undefined);
|
||||||
30
js/src/jit-test/tests/realms/promise-then.js
Normal file
30
js/src/jit-test/tests/realms/promise-then.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
|
const g = newGlobal({sameCompartmentAs: this});
|
||||||
|
|
||||||
|
let resolve, reject;
|
||||||
|
let promise = new Promise((resolveFn, rejectFn) => {
|
||||||
|
resolve = resolveFn;
|
||||||
|
reject = rejectFn;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set to a built-in Promise.prototype.then function, but from a different realm.
|
||||||
|
promise.then = g.Promise.prototype.then;
|
||||||
|
|
||||||
|
// Make SpeciesConstructor throw a TypeError exception.
|
||||||
|
promise.constructor = {
|
||||||
|
[Symbol.species]: "not a constructor"
|
||||||
|
};
|
||||||
|
|
||||||
|
async function f(p) {
|
||||||
|
await p;
|
||||||
|
}
|
||||||
|
|
||||||
|
let error;
|
||||||
|
f(promise).catch(e => { error = e; });
|
||||||
|
|
||||||
|
resolve(promise);
|
||||||
|
|
||||||
|
drainJobQueue();
|
||||||
|
|
||||||
|
assertEq(error.constructor === g.TypeError, true);
|
||||||
|
|
@ -721,8 +721,8 @@ JSRuntime::enqueuePromiseJob(JSContext* cx, HandleFunction job, HandleObject pro
|
||||||
if (promise) {
|
if (promise) {
|
||||||
RootedObject unwrappedPromise(cx, promise);
|
RootedObject unwrappedPromise(cx, promise);
|
||||||
// While the job object is guaranteed to be unwrapped, the promise
|
// While the job object is guaranteed to be unwrapped, the promise
|
||||||
// might be wrapped. See the comments in
|
// might be wrapped. See the comments in EnqueuePromiseReactionJob in
|
||||||
// intrinsic_EnqueuePromiseReactionJob for details.
|
// builtin/Promise.cpp for details.
|
||||||
if (IsWrapper(promise))
|
if (IsWrapper(promise))
|
||||||
unwrappedPromise = UncheckedUnwrap(promise);
|
unwrappedPromise = UncheckedUnwrap(promise);
|
||||||
if (unwrappedPromise->is<PromiseObject>())
|
if (unwrappedPromise->is<PromiseObject>())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue