Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
glebec opened this issue Jan 1, 2017 · 5 comments
Closed

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

glebec opened this issue Jan 1, 2017 · 5 comments

Comments

@glebec
Copy link
Contributor

glebec commented Jan 1, 2017

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:

  • If a thenable is passed to the resolution procedure, is it supposed to lock out subsequent resolution calls? Or is it not supposed to? Or does it not matter?
  • If the resolution procedure is supposed to lock to the first value passed — even if a thenable — then Potential seems to reveal a hole in the compliance specs. Is this confirmed?
@glebec
Copy link
Contributor Author

glebec commented Jan 1, 2017

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
Copy link
Member

The first resolve call should always win.

@Nazimkhan123
Copy link

please halp

@glebec
Copy link
Contributor Author

glebec commented May 21, 2017

@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.

@glebec glebec closed this as completed May 21, 2017
@Nazimkhan123
Copy link

Nazimkhan123 commented May 21, 2017 via email

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants