问题
I'm just trying to improve my understanding on how JavaScript Promises work. I've created the following situation:
LOG 'FOO'
RUN CALLBACK LOGGING 'CALLBACK'
LOG 'BAR'
Expect all functions to complete immediately (by this I mean they will not take an excessive/unknown amount of time to complete that you would use an async operation to complete) so that the above order of operations will happen in that order.
You can write this in the following way:
function foo(cb) {
// LOG 'FOO'
console.log('foo');
// RUN CALLBACK
cb();
}
function callback() {
// LOG 'CALLBACK'
console.log('callback');
}
foo(callback);
console.log('bar');
This produces the expected output according to the situation I specified at the beginning.
> foo
> callback
> bar
You could also write it in the following way:
function foo() {
return new Promise((resolve) => {
// LOG 'FOO'
console.log('foo');
return resolve(null);
});
}
function callback() {
// LOG 'CALLBACK'
console.log('callback');
}
foo().then(callback);
// LOG 'BAR'
console.log('bar');
This situation produces the following result:
> foo
> bar
> callback
This is where I am unclear as I am expecting foo
to have completed immediately so that callback
will run and log 'callback'
before bar
logs 'bar'
回答1:
The relevant specs are here:
Promises/A+ point 2.2.4:
onFulfilled
oronRejected
must not be called until the execution context stack contains only platform code. [3.1].And note 3.1 (emphasis mine):
Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that
onFulfilled
andonRejected
execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such assetTimeout
orsetImmediate
, or with a “micro-task” mechanism such asMutationObserver
orprocess.nextTick
. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.ECMAScript 6.0 (based on Promises/A+) is a little harder to excerpt cleanly, but
then
resolves as in section 25.4.5.3.1:Else if the value of promise's [[PromiseState]] internal slot is
"fulfilled"
,a. Let value be the value of promise's [[PromiseResult]] internal slot.
b. Perform EnqueueJob(
"PromiseJobs"
, PromiseReactionJob, «fulfillReaction, value»).Else if the value of promise's [[PromiseState]] internal slot is
"rejected"
,a. Let reason be the value of promise's [[PromiseResult]] internal slot.
b. Perform EnqueueJob(
"PromiseJobs"
, PromiseReactionJob, «rejectReaction, reason»).
And the important EnqueueJob operation is defined in section 8.4 ("Jobs and Job Queues"), featuring this in its preface (bold is mine):
Execution of a Job can be initiated only when there is no running execution context and the execution context stack is empty. [...] Once execution of a Job is initiated, the Job always executes to completion. No other Job may be initiated until the currently running Job completes.
In practice, this lets you make a few simple and consistent statements:
- You can count on
then
orcatch
(etc) to always behave asynchronously, never synchronously. - You'll never see multiple
then
orcatch
handlers on the same stack, even if one Promise is explicitly resolved within another Promise. This also means that recursive Promise execution doesn't risk stack overflows as a normal function call might, though you can still run out of heap space if you're careless with recursive closures in a pathological case. - Time-consuming operations queued in a
then
orcatch
handler will never block the current thread, even if the Promise is already settled, so you can queue up a number of asynchronous operations without worrying about the order or promise state. - There will never be an enclosing
try
block outside of athen
orcatch
, even when callingthen
on an already-settled Promise, so there's no ambiguity about whether the platform should handle a thrown exception.
回答2:
This is not possible due to the way promises works. Even promises which immediate resolves runs for the next tick, what you want is sync functions not promises.
See this for example:
setTimeout(function() {
console.log("test");
}, 0);
console.log("test2");
It is impossible to print test before test2 without removing the setTimeout
function, because even if the wait parameter is 0, it will run for the next tick which means that it will run when all sync code has run.
回答3:
I really don't mean to be blunt, but it is because that is the way the spec says that they work. If you have a need for a piece of code to run at a certain point after a code within a promise finishes, then you should utilize the promise chain. Once you introduce asynchronous code into the mix, it is a bad idea to try and mix it with dependent, synchronous, code.
Daisy chain promises whenever you need things to depend on asynchronous code:
function foo() {
console.log('foo');
return Promise.resolve();
}
function callback() {
console.log('callback');
}
function consoler() {
console.log('bar');
}
foo().then(callback).then(consoler);
Produces:
foo
callback
bar
来源:https://stackoverflow.com/questions/38059284/why-does-javascript-promise-then-handler-run-after-other-code