mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-04 23:08:39 +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) {
|
||||
RootedObject unwrappedPromise(cx, promise);
|
||||
// While the job object is guaranteed to be unwrapped, the promise
|
||||
// might be wrapped. See the comments in
|
||||
// intrinsic_EnqueuePromiseReactionJob for details.
|
||||
// might be wrapped. See the comments in EnqueuePromiseReactionJob in
|
||||
// builtin/Promise.cpp for details.
|
||||
if (IsWrapper(promise))
|
||||
unwrappedPromise = UncheckedUnwrap(promise);
|
||||
if (unwrappedPromise->is<PromiseObject>())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue