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

Why does JavaScript Promise then handler run after other code?

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

LOG 'FOO'
RUN CALLBACK LOGGING 'CALLBACK'
LOG 'BAR'

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

你可以这样写:

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

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

foo(callback);

console.log('bar');

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

> foo
> callback
> bar

也可以这样写:

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');

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

> foo
> bar
> callback

这是我不清楚的地方,因为我希望 foo 立即完成 ,以便 callback 将 运行 并记录 'callback'bar 之前记录 'bar'

由于 promises 的工作方式,这是不可能的。 即使 promises 立即解决下一个 tick 运行s,你想要的是同步功能而不是 promises。

例如看这个:

setTimeout(function() {
    console.log("test");
}, 0);


console.log("test2");

不去掉setTimeout函数是不可能在test2之前打印test的,因为即使wait参数为0,下一个tick也会运行,也就是说会运行 当所有同步码都有 运行.

我真的不想直言不讳,但这是因为规范说明它们是这样工作的。如果您需要一段代码在 promise 中的代码完成后的某个时刻 运行,那么您应该使用 promise 链。一旦将异步代码引入组合中,尝试将其与依赖的同步代码混合是一个坏主意。

只要您需要依赖异步代码的东西,菊花链承诺:

function foo() {
    console.log('foo');
    return Promise.resolve();
}

function callback() {
    console.log('callback');
}

function consoler() {
    console.log('bar');
}

foo().then(callback).then(consoler);

产生:

foo
callback
bar

相关规格在这里:

  1. Promises/A+ point 2.2.4:

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

    注意 3.1(强调我的):

    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. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.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.

  2. 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:

    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»).

    2. 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»).

    并且重要的 EnqueueJob 操作在 section 8.4 ("Jobs and Job Queues") 中定义,在其前言中对此进行了说明(粗体是我的):

    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.

实际上,这可以让您做出一些简单而一致的陈述:

  • 您可以指望 thencatch(等)始终 异步运行,从不 同步运行.
  • 你永远不会在同一个堆栈上看到多个 thencatch 处理程序,即使一个 Promise 在另一个 Promise 中明确解决。这也意味着递归 Promise 执行不会像普通函数调用那样冒堆栈溢出的风险,但如果您在病态情况下不小心使用递归闭包,您仍然可以 运行 超出堆 space 。
  • thencatch处理程序中排队的耗时操作永远不会阻塞当前线程,即使Promise已经确定,所以你可以排队一些异步操作而不用担心订单或承诺状态。
  • thencatch 之外永远不会有一个封闭的 try 块,即使在已经解决的 Promise 上调用 then 时也是如此,所以没有关于平台是否应该处理抛出的异常的歧义。