为什么 JavaScript Promise 然后处理程序在其他代码之后运行?

Win*_*ing 5 javascript promise ecmascript-6 es6-promise

我只是想提高我对 JavaScript Promises 工作原理的理解。我创造了以下情况:

LOG 'FOO'
RUN CALLBACK LOGGING 'CALLBACK'
LOG 'BAR'
Run Code Online (Sandbox Code Playgroud)

期望所有功能立即完成(我的意思是它们不会花费过多/未知的时间来完成,您将使用异步操作来完成)以便上述操作顺序将按该顺序发生。

您可以通过以下方式编写:

function foo(cb) {
  // LOG 'FOO'
  console.log('foo');
  // RUN CALLBACK
  cb();
}

function callback() {
  // LOG 'CALLBACK'
  console.log('callback');
}

foo(callback);

console.log('bar');
Run Code Online (Sandbox Code Playgroud)

这会根据我在开始时指定的情况产生预期的输出。

> foo
> callback
> bar
Run Code Online (Sandbox Code Playgroud)

可以通过以下方式编写它:

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');
Run Code Online (Sandbox Code Playgroud)

这种情况会产生以下结果:

> foo
> bar
> callback
Run Code Online (Sandbox Code Playgroud)

这是我不清楚我期待foo已完成,立即使callback运行和日志'callback'之前bar日志'bar'

Jef*_*ica 5

相关规格在这里:

  1. 承诺/A+点2.2.4

    onFulfilled或者onRejected在执行上下文堆栈仅包含平台代码之前不得调用。[3.1]。

    并注意 3.1(强调我的):

    这里的“平台代码”是指引擎、环境和promise实现代码。在实践中,这个要求确保onFulfilledonRejected异步执行,在调用 then 的事件循环之后,并使用新的堆栈。这可以通过“宏任务”机制(例如setTimeoutsetImmediate)或“微任务”机制(例如MutationObserver或 )来实现process.nextTick。由于promise实现被认为是平台代码,它本身可能包含一个任务调度队列或“trampoline”,在其中调用处理程序。

  2. 的ECMAScript 6.0(基于承诺/ A +)是有点难以摘录干净,但then做出决议如在章节25.4.5.3.1

    1. 否则,如果promise的 [[PromiseState]] 内部槽的值为"fulfilled"

      一种。让valuepromise的 [[PromiseResult]] 内部槽的值。

      湾 执行 EnqueueJob( "PromiseJobs", PromiseReactionJob, «?fulfillReaction, value» )。

    2. 否则,如果 promise 的 [[PromiseState]] 内部槽的值为"rejected"

      一种。让reasonpromise的 [[PromiseResult]] 内部槽的值。

      湾 执行 EnqueueJob( "PromiseJobs", PromiseReactionJob, «?rejectReaction, reason» )。

    重要的 EnqueueJob 操作在第 8.4 节(“作业和作业队列”)中定义,在其序言中对此进行了说明(粗体是我的):

    只有当没有正在运行的执行上下文且执行上下文堆栈为空时,才能启动 Job 的执行。[...] 一旦 Job 开始执行,Job 总是执行到完成。在当前运行的作业完成之前,不能启动其他作业。

在实践中,这可以让您做出一些简单且一致的陈述:

  • 您可以依靠thencatch(等)始终异步运行,而不是同步运行。
  • 即使在另一个 Promise 中明确解析了一个 Promise,您也永远不会在同一堆栈上看到多个thencatch处理程序。这也意味着递归 Promise 执行不会像普通函数调用那样冒堆栈溢出的风险,尽管如果您在病态情况下不小心使用递归闭包,您仍然可能会耗尽堆空间。
  • thencatch处理程序中排队的耗时操作永远不会阻塞当前线程,即使 Promise 已经结算,因此您可以将多个异步操作排队,而无需担心订单或承诺状态。
  • 永远不会有try一个thenor之外的封闭块catch,即使在调用then已经解决的 Promise 时也是如此,因此平台是否应该处理抛出的异常没有歧义。