为什么JavaScript承诺然后处理程序运行在其他代码之后? [英] Why does JavaScript Promise then handler run after other code?
问题描述
我只是想提高我对JavaScript承诺工作原理的理解。我创建了以下情况:
LOG'FOO'
运行回拨LOGGING'CALLBACK'
LOG'BAR'
期望所有功能立即完成 意味着他们不会花费过多/未知的时间来完成您将使用异步操作来完成),以便上述操作顺序将按照该顺序进行。
你可以这样写:
function foo(cb){
// LOG'FOO '
console.log('foo');
// RUN CALLBACK
cb();
}
函数callback(){
// LOG'CALLBACK'
console.log('callback');
}
foo(回调);
console.log('bar');
根据我在开头指定的情况产生预期输出。
> foo
>回调
> bar
您可以也可以按以下方式写:
function foo(){
return new Promise((resolve)=> {
// LOG'FOO '
console.log('foo');
return resolve(null);
});
}
函数callback(){
// LOG'CALLBACK'
console.log('callback');
}
foo()。then(callback);
// LOG'BAR'
console.log('bar');
此情况产生以下结果:
> foo
> bar
>回调
这是我不清楚的地方,我期待着 foo
立即完成,以便回调
将运行并记录'callback'
在栏之前
日志'bar'
相关规范在这里:
-
<$在执行上下文堆栈只包含平台代码之前,不得调用c $ c> onFulfilled 或
onRejected
。 [3.1]。
注意3.1(强调我的):
这里的平台代码是指引擎,环境和承诺实现代码。 实际上,这个要求确保事件循环之后和
onRejected
异步执行转入,然后被称为,并用一个新的堆栈。这可以使用宏任务机制来实现,例如setTimeout
或setImmediate
,或者使用micro -task机制,例如MutationObserver
或process.nextTick
。由于承诺实现被认为是平台代码,它本身可能包含一个任务调度队列或蹦床,其中调用处理程序。
-
ECMAScript 6.0 (基于Promises / A +)是有点难以摘录,但
然后
解析如第25.4.5.3.1节:
-
否则,如果承诺的[[PromiseState]]内部插槽的值为
满足
p>
a。让值为承诺的[[PromiseResult]]内部插槽的值。
b。执行EnqueueJob(
PromiseJobs
,PromiseReactionJob,fulfillReaction,value»)。 -
否则promise的[[PromiseState]]内部插槽的值为
reject
,
一个。让原因成为承诺的[[PromiseResult]]内部插槽的值。
b。执行EnqueueJob(
PromiseJobs
,PromiseReactionJob,«rejectReaction,reason»)。
重要的EnqueueJob操作在第8.4节(作业和作业队列),其前言(粗体是我的):
只有在没有执行上下文并且执行上下文堆栈为空的情况下,才能执行作业。 ..]一旦作业执行启动,作业总是执行完成。 在当前正在运行的作业完成之前不会启动任何其他作业。
-
实际上,这可以让您做出简单而一致的陈述:
- 您可以依靠
然后
或catch
(etc)to 始终异步运行,永远不会同步。 / li>
- 你不会在同一个堆栈上看到多个
然后
或catch
处理程序即使在另一个承诺中明确地解决了一个承诺。这也意味着递归Promise执行并不会像正常的函数调用那样冒着堆栈溢出,尽管如果您在病理情况下不经意地执行递归关闭,那么仍然可以用尽堆空间。 - 在
然后
或catch
处理程序中排队的耗时的操作将永远不会阻止当前线程,即使承诺已经解决了,所以您可以排队一些异步操作,而不用担心订单或承诺状态。 - 永远不会有一个封闭的
try
阻止在
之外,然后
或catch
,即使调用然后
在一个已经定居的承诺,所以没有歧义的平台是否应该处理抛出的例外。
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'
The relevant specs are here:
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.
这篇关于为什么JavaScript承诺然后处理程序运行在其他代码之后?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!