promises-aplus / promises-spec

An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.
https://promisesaplus.com/
Creative Commons Zero v1.0 Universal
1.84k stars 164 forks source link

Clarify ambiguity between promises and thenables w.r.t. 2.3.2.1? #246

Closed glebec closed 7 years ago

glebec commented 7 years ago

2.3.2.1 If x is pending, promise must remain pending until x is fulfilled or rejected.

As I understand it, one effect of this rule is that the promise resolution procedure becomes "locked" to the first value/call, even if the passed-in value is itself a promise, and the promise resolution procedure is somehow triggered again with a normal value. To illustrate:

Example A: all promises (2.3.2.1)

const x = new Promise(resolve => {
  setTimeout(() => resolve('2.3.2.1 says wait for me'), 1000);
})

const promise = new Promise(resolve => {
  resolve(x);
  resolve('Will I win against x, in defiance of 2.3.2.1?');
});

promise.then(console.log.bind(console)); // 2.3.2.1 says wait for me

However, 2.3.2.1 refers specifically to known promises (e.g. library constructs). I may be misreading something, but the spec doesn't seem to conclusively enforce that promise should wait on the eventual value of a thenable to the exclusion of subsequent resolutions. 2.3.3.3 and 2.3.3.3.3 come close:

2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:

2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.

Were resolvePromise and rejectPromise in this context intended to be singletons shared across all promise resolution procedure calls? The spec doesn't seem to enforce that. Whether this is an ambiguity or an inconsistency, libraries which pass the compliance tests may exhibit different behavior. Let's look at some examples:

Example B1: Potential thenable, V8 promise

const Potential = require('potential');

const x = new Potential(resolve => {
  setTimeout(() => resolve('If thenables behave like 2.3.2.1, wait for me'), 1000);
});

const promise = new Promise(resolve => {
  resolve(x);
  resolve('Aha, I have beat x to the punch, since 2.3.2.1 does not apply to thenables');
});

promise.then(console.log.bind(console)); // If thenables behave like 2.3.2.1, wait for me

V8 Promises enforce waiting on thenables to the exclusion of later resolve calls, similar to 2.3.2.1. However…

Example B2: V8 thenable, Potential promise

const Potential = require('potential');

const x = new Promise(resolve => {
  setTimeout(() => resolve('If thenables behave like 2.3.2.1, wait for me'), 1000);
});

const promise = new Potential(resolve => {
  resolve(x);
  resolve('Aha, I have beat x to the punch, since 2.3.2.1 does not apply to thenables');
});

promise.then(console.log.bind(console)); // Aha, I have beat x to the punch, since 2.3.2.1 does not apply to thenables

Swap the roles around, and Potential has different behavior from V8 despite passing the A+ compliance tests. Is this a hole in the tests, or something that is intended to go either way depending on the whims of the library?

Others

Bluebird behaves like V8 in this respect. So do Q, ayepromise, avow, when, rsvp, and aplus-promesse.

Conclusion

To summarize:

glebec commented 7 years ago

So after all that, I realized this might not fall under the purview of the spec at all. In the spec, the resolution procedure for a downstream promise only runs when an upstream promise handler returns a value (which can, of course, only happen once). So… what might be the "spirit of the spec" in this case?

ForbesLindesay commented 7 years ago

The first resolve call should always win.

Nazimkhan123 commented 7 years ago

please halp

glebec commented 7 years ago

@Nazimkhan123 — help with what?

Also: I am closing this issue as being related, but ultimately falling outside of, the spec. At most I would say that the spec could include a footnote suggesting that the answer by @ForbesLindesay.

Nazimkhan123 commented 7 years ago

pro