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

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

问题描述

我只是想提高我对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'

解决方案

相关规范在这里:


  1. Promises / A + point 2.2.4


    <$在执行上下文堆栈只包含平台代码之前,不得调用c $ c> onFulfilled 或 onRejected 。 [3.1]。


    注意3.1(强调我的):


    这里的平台代码是指引擎,环境和承诺实现代码。 实际上,这个要求确保事件循环之后和 onRejected 异步执行转入,然后被称为,并用一个新的堆栈。这可以使用宏任务机制来实现,例如 setTimeout setImmediate ,或者使用micro -task机制,例如 MutationObserver process.nextTick 。由于承诺实现被认为是平台代码,它本身可能包含一个任务调度队列或蹦床,其中调用处理程序。



  2. ECMAScript 6.0 (基于Promises / A +)是有点难以摘录,但然后解析如第25.4.5.3.1节



    1. 否则,如果承诺的[[PromiseState]]内部插槽的值为满足 p>

      a。让承诺的[[PromiseResult]]内部插槽的值。



      b。执行EnqueueJob(PromiseJobs,PromiseReactionJob,fulfillReaction,value»)。


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

  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承诺然后处理程序运行在其他代码之后?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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