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

What is the intention behind clause 2.2.4 of Promise/A+ spec? #229

Open
cuipengfei opened this issue Apr 29, 2016 · 5 comments
Open

What is the intention behind clause 2.2.4 of Promise/A+ spec? #229

cuipengfei opened this issue Apr 29, 2016 · 5 comments

Comments

@cuipengfei
Copy link
Contributor

cuipengfei commented Apr 29, 2016

Clause 2.2.4 of the promise/a+ spec says:

onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

Then in the notes it states that:

Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.

Is the intention of this to ensure that when there is a large amount of onFulfilled functions in a chain, the execution of them does not cause the thread to block?

Or is there anything else that is between the lines that I am not reading?

@glebec
Copy link
Contributor

glebec commented Jul 11, 2016

The blocking behavior you describe is certainly one advantage. It's also not totally obvious, but this spec also means that promise handlers execute in a "breadth-first" rather than "depth-first" way — for a given promise pA, acting as the head of several independent promise chains (result of calling pA.then multiple times), the first-level handlers will all execute before the second-level handlers (handlers attached to the promises pB, pC etc. returned by each pA.then). In other words, all of pA's handlers execute before any promise handlers further down in each chain. Whether that was part of the intent of the spec writers I cannot say but it does feel like the "correct" behavior to me.

More generally, it seems to me that the note you quote says the intent clearly enough: it increases deterministic behavior of promises. Handlers will always execute asynchronously, which is easier to reason about than if they sometimes execute synchronously and sometimes don't.

@bergus
Copy link

bergus commented Jul 11, 2016

In other words, all of pA's handlers execute before any promise handlers further down in each chain.

No, it does not mean that. This might be a behaviour implemented by ES6 Promises and other libraries that use a queue-style asynchronous execution, but it's not required by A+. Btw, this breaks apart anyway when you register handlers after resolution.

@glebec
Copy link
Contributor

glebec commented Jul 11, 2016

Ah, my bad! I conflated the "handlers must run in order" and "handlers must be called asynchronously" into a single "handlers must run in order in one async step" concept. Of course if each handler is scheduled independently there can be interleaving without breaking P/A+. Thanks!

And I should have made it clear I was talking about existing promise chains pre-resolution. :-P

@FGasper
Copy link

FGasper commented Oct 31, 2019

If I may “jiggle” this thread a bit:

What is the benefit of ensuring that handlers be called asynchronously? I’ve got a promise implementation that I’m using to abstract over whether a given library works synchronously or asynchronously. It was pointed out to me that my implementation, by firing handlers synchronously, violates Promise/A+, but other than the mere fact of that violation, what problems might I find with the approach I’m taking?

@glebec
Copy link
Contributor

glebec commented Oct 31, 2019

@FGasper one reason to require async invocation of handlers is that it (perhaps counter-intuitively) makes your code more deterministic.

somePromise.then(handlerA)
doThingB()

If handlers are sometimes called sync and sometimes async, you cannot make any statements about ordering between handlerA and doThingB above. If handlers are always called async, then you know B will happen before A.

This may seem like a subtle issue, but it prevents users from baking in accidental race conditions where they mistakenly think A will always happen before B, because it happened one time that they tested it.

There may be other, better reasons for this requirement. This is just one I happen to think applies.

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

4 participants