为什么 JavaScript Promise 然后处理程序在其他代码之后运行? [英] Why does JavaScript Promise then handler run after other code?

查看:11
本文介绍了为什么 JavaScript Promise 然后处理程序在其他代码之后运行?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

LOG 'FOO'运行回调记录回调"记录酒吧"

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

你可以这样写:

function foo(cb) {//记录'FOO'console.log('foo');//运行回调cb();}函数回调(){//记录'回调'console.log('回调');}富(回调);console.log('bar');

这会根据我在开头指定的情况产生预期的输出.

>富>打回来>酒吧

也可以用以下方式编写:

function foo() {返回新的承诺((解决)=> {//记录'FOO'console.log('foo');返回解决(空);});}函数回调(){//记录'回调'console.log('回调');}foo().then(回调);//记录'条'console.log('bar');

这种情况产生以下结果:

>富>酒吧>打回来

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

解决方案

相关规范在这里:

  1. Promises/A+ 点 2.2.4:

    <块引用>在执行上下文堆栈仅包含平台代码之前,不得调用

    onFulfilledonRejected.[3.1].

    注意 3.1(强调我的):

    <块引用>

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

  2. ECMAScript 6.0(基于 Promises/A+)摘录干净有点困难,但是 then 解决了 如第 25.4.5.3.1 节:

    <块引用>

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

      一个.让 value 成为 promise 的 [[PromiseResult]] 内部槽的值.

      B.执行 EnqueueJob("PromiseJobs", PromiseReactionJob, «‍fulfillReaction, value»).

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

      一个.让 reason 成为 promise 的 [[PromiseResult]] 内部槽的值.

      B.执行 EnqueueJob("PromiseJobs", PromiseReactionJob, «‍rejectReaction, reason»).

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

    <块引用>

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

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

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

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:

  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].

    And note 3.1 (emphasis mine):

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

    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 or catch (etc) to always behave asynchronously, never synchronously.
  • You'll never see multiple then or catch 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 or catch 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 a then or catch, even when calling then on an already-settled Promise, so there's no ambiguity about whether the platform should handle a thrown exception.

这篇关于为什么 JavaScript Promise 然后处理程序在其他代码之后运行?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆