Ecma262: Lookup `resolve` property only once in PerformPromiseAll and PerformPromiseRace

Created on 10 Apr 2019  Â·  15Comments  Â·  Source: tc39/ecma262

See https://github.com/tc39/proposal-promise-allSettled/pull/40 for background

This would be a change similar to the one proposed with Promise.allSettled

Most helpful comment

var promise = new Promise(function() {});

var resolveCount = 0
var proxy = new Proxy(Promise, {
    get(_, name) {
        if (name === 'resolve') {
            resolveCount++;
        }
        return Promise[name];
    }
});

Promise.all.call(proxy, [promise, promise, promise]);

console.log(resolveCount); // should be 1 after this change, it's currently 3

If you prefer avoiding Proxies:

var promise = new Promise(function() {});
var resolveCount = 0;

function NotPromise(executor) {
    return new Promise(executor);
}

Object.defineProperty(NotPromise, 'resolve', {
    get() {
        resolveCount++;
        return function(v) {
            return Promise.resolve(v);
        };
    }
});

Promise.all.call(NotPromise, [promise, promise, promise]);

console.log(resolveCount);

Here are some tests you can use for Promise.all and Promise.allSettled. Promise.race would need a few tweaks.

I support this change.


You can also monkeypatch Promise.resolve directly but make sure it does not conflict with other stuff while running the tests:

var promise = new Promise(function() {});
var resolveCount = 0;
var origResolve = Promise.resolve;

Object.defineProperty(Promise, 'resolve', {
    get() {
        resolveCount++;
        return origResolve;
    }
});

Promise.all([promise, promise, promise])

console.log(resolveCount);

All 15 comments

This change seems consistent with other parts of the language: for example, the Map constructor (and related constructors elsewhere) look up "set" exactly once, outside of the loop.

It’s also in the spirit of changes we’ve made after-the-fact to the iterator protocol, and then some: #976, #988, #1021, #1398, #1408.

I’d love to see it happen; do we have any data about web compatibility?

For this to break anyone, they would need to be replacing Promise.resolve with a not-semantically-equivalent version as a side effect of an iterator passed to Promise.{all, race}, right? (edit: or making access to {PromiseOrPromiseSubclass}.resolve be side-effecting, as Leo points out below.) That seems like the sort of thing which is unlikely to be happening in practice.

I would love web compat data, but this seems like one of those cases where we could decide to make the change without data and hope engines don't find they need to back it out (assuming of course that engines were on board).

If we’re all comfortable with that, then i am too :-) i agree it seems highly unlikely.

agreed

var promise = new Promise(function() {});

var resolveCount = 0
var proxy = new Proxy(Promise, {
    get(_, name) {
        if (name === 'resolve') {
            resolveCount++;
        }
        return Promise[name];
    }
});

Promise.all.call(proxy, [promise, promise, promise]);

console.log(resolveCount); // should be 1 after this change, it's currently 3

If you prefer avoiding Proxies:

var promise = new Promise(function() {});
var resolveCount = 0;

function NotPromise(executor) {
    return new Promise(executor);
}

Object.defineProperty(NotPromise, 'resolve', {
    get() {
        resolveCount++;
        return function(v) {
            return Promise.resolve(v);
        };
    }
});

Promise.all.call(NotPromise, [promise, promise, promise]);

console.log(resolveCount);

Here are some tests you can use for Promise.all and Promise.allSettled. Promise.race would need a few tweaks.

I support this change.


You can also monkeypatch Promise.resolve directly but make sure it does not conflict with other stuff while running the tests:

var promise = new Promise(function() {});
var resolveCount = 0;
var origResolve = Promise.resolve;

Object.defineProperty(Promise, 'resolve', {
    get() {
        resolveCount++;
        return origResolve;
    }
});

Promise.all([promise, promise, promise])

console.log(resolveCount);

I’m with the “let’s just do it [in V8]” crowd. Agreed that the change seems obscure enough that I wouldn’t expect any significant breakage.

I plan on merging https://github.com/tc39/proposal-promise-allSettled/pull/40.

@gsathya do you want to kick off a PR against ecma262 as well?

@gsathya do you want to kick off a PR against ecma262 as well?

Done: https://github.com/tc39/ecma262/pull/1506

@leobalter Can you please land your tests in test262? I'd like to have good test262 coverage before shipping this in chrome. Thanks!

https://github.com/tc39/proposal-promise-allSettled/pull/40 is now merged. Let's land the Test262 tests and get this optimization shipped in Chrome soon, so we can gather data. Thanks, everyone!

We have tests in https://github.com/tc39/test262/pull/2131.

I'd like to discuss if we should change this behavior to only get the 'resolve' method if the iterable is not empty. Do we have precedent for this?

It feels like there'd be more precedent for minimize calling user code before checking all invariants; iow, if resolve isn't callable, why bother invoking next at all? (to check if the iterable is empty)

@leobalter:

I'd like to discuss if we should change this behavior to only get the 'resolve' method if the iterable is not empty. Do we have precedent for this?

I think the closest thing is the behavior of things like the Map or Set constructors, which are precedent in the other direction: they look up their adder function before consuming the iterator at all, even to check if it's empty (i.e., pretty much what #1506 does). I wouldn't think that precedent needs to be binding, if there's a reason to do things differently, but on the other hand I think the currently proposed behavior is more predictable (and it sounds like e.g. @gibson042 agrees).

sounds reasonable. I just wanted to make sure the tests represent the proposed intent. Sounds like it already does.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jorendorff picture jorendorff  Â·  4Comments

ursi picture ursi  Â·  4Comments

axross picture axross  Â·  4Comments

erights picture erights  Â·  4Comments

wangyi7099 picture wangyi7099  Â·  5Comments