在javascript承诺中执行的顺序是什么 [英] What is the order of execution in javascript promises

查看:119
本文介绍了在javascript承诺中执行的顺序是什么的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想向自己解释下面使用javascript promises的代码段的执行顺序。

I'd like to explain to myself the execution order of the following snippet that uses javascript promises.

Promise.resolve('A')
  .then(function(a){console.log(2, a); return 'B';})
  .then(function(a){
     Promise.resolve('C')
       .then(function(a){console.log(7, a);})
       .then(function(a){console.log(8, a);});
     console.log(3, a);
     return a;})
  .then(function(a){
     Promise.resolve('D')
       .then(function(a){console.log(9, a);})
       .then(function(a){console.log(10, a);});
     console.log(4, a);})
  .then(function(a){
     console.log(5, a);});
console.log(1);
setTimeout(function(){console.log(6)},0);

结果是:

1
2 "A"
3 "B"
7 "C"
4 "B"
8 undefined
9 "D"
5 undefined
10 undefined
6

我很好奇执行顺序1 2 3 7 ...而不是值'A','B'...

I'm curious about the execution order 1 2 3 7... not the values 'A', 'B'...

我的理解是,如果一个承诺解决'then'函数被放入浏览器事件队列。所以我的期望是1 2 3 4 ...

My understanding is that if a promise is resolved the 'then' function is put in the browser event queue. So my expectation was 1 2 3 4 ...

@ jfriend00谢谢,非常感谢您的详细解释!这真的是一项巨大的工作!

@jfriend00 Thanks, thanks a lot for the detailed explanations! It's really an enormous amount of work!

推荐答案

评论

首先,在 .then()处理程序中运行promises,而不是从 .then()<返回这些promises / code> callback创建一个全新的未附加的promise序列,它不会以任何方式与父promises同步。通常,这是一个错误,事实上,一些承诺引擎实际上会在您这样做时发出警告,因为它几乎不是所希望的行为。人们想做的唯一一次就是当你做某种火灾并忘记操作时你不关心错误而你不关心与世界其他地方的同步。

First off, running promises inside of a .then() handler and NOT returning those promises from the .then() callback creates a completely new unattached promise sequence that is not synchronized with the parent promises in any way. Usually, this is a bug and, in fact, some promise engines actually warn when you do that because it is almost never the desired behavior. The only time one would ever want to do that is when you're doing some sort of fire and forget operation where you don't care about errors and you don't care about synchronizing with the rest of the world.

那么,所有 Promise.resolve()承诺在 .then()处理程序创建独立于父链运行的新Promise链。您没有确定的行为。这有点像并行启动四个ajax调用。你不知道哪一个会先完成。现在,因为那些 Promise.resolve()处理程序中的所有代码恰好是同步的(因为这不是真实的代码),那么你可能会获得一致的行为,但是这不是承诺的设计点所以我不会花太多时间试图找出只运行同步代码的Promise链将首先完成。在现实世界中,这并不重要,因为如果订单很重要,那么你就不会以这种方式让事情发生。

So, all your Promise.resolve() promises inside of .then() handlers create new Promise chains that run independently of the parent chain. You do not have a determinate behavior. It's kind of like launching four ajax calls in parallel. You don't know which one will complete first. Now, since all your code inside those Promise.resolve() handlers happens to be synchronous (since this isn't real world code), then you might get consistent behavior, but that isn't the design point of promises so I wouldn't spend much time trying to figure out which Promise chain that runs synchronous code only is going to finish first. In the real world, it doesn't matter because if order matters, then you won't leave things to chance this way.

摘要


  1. 所有 .then()处理程序在当前后异步调用执行线程结束(正如Promises / A +规范所说,当JS引擎返回平台代码时)。即使对于同步解析的promises也是如此,例如 Promise.resolve()。then(...)。这是为了编程一致性,因此无论是立即解决还是稍后解决,都会异步调用 .then()处理程序。这可以防止一些计时错误,并使调用代码更容易看到一致的异步执行。

  1. All .then() handlers are called asynchronously after the current thread of execution finishes (as the Promises/A+ spec says, when the JS engine returns back to "platform code"). This is true even for promises that are resolved synchronously such as Promise.resolve().then(...). This is done for programming consistency so that a .then() handler is consistently called asynchronously no matter whether the promise is resolved immediately or later. This prevents some timing bugs and makes it easier for the calling code to see consistent asynchronous execution.

没有规范确定<$ c的相对顺序$ c> setTimeout()与预定 .then()处理程序如果两者都已排队并准备好运行。在您的实现中,挂起的 .then()处理程序总是在挂起的 setTimeout()之前运行,但是Promises / A +规范说明这不是确定的。它说 .then()处理程序可以安排多种方式,其中一些在挂起 setTimeout()调用,其中一些可能在挂起 setTimeout()调用后运行。例如,Promises / A +规范允许使用 setImmediate()来安排 .then()处理程序在挂起 setTimeout()调用之前运行或使用 setTimeout()在挂起 setTimeout之后运行()来电。因此,您的代码根本不应该依赖于该订单。

There is no specification that determines the relative order of setTimeout() vs. scheduled .then() handlers if both are queued and ready to run. In your implementation, a pending .then() handler is always run before a pending setTimeout(), but the Promises/A+ spec specification says this is not determinate. It says that .then() handlers can be scheduled a whole bunch of ways, some of which would run before pending setTimeout() calls and some of which might run after pending setTimeout() calls. For example, the Promises/A+ spec allows .then() handlers to be scheduled with either setImmediate() which would run before pending setTimeout() calls or with setTimeout() which would run after pending setTimeout() calls. So, your code should not depend upon that order at all.

多个独立的Promise链没有可预测的执行顺序,您不能依赖任何特定的订购。这就像并行发射四个ajax调用,你不知道哪一个会先完成。

Multiple independent Promise chains do not have a predictable order of execution and you cannot rely on any particular order. It's like firing off four ajax calls in parallel where you don't know which one will complete first.

如果执行顺序很重要,不要创建一个种族依赖于细微的实施细节。相反,链接承诺链以强制执行特定的执行顺序。

If order of execution is important, do not create a race that is dependent upon minute implementation details. Instead, link promise chains to force a particular execution order.

您通常不希望在中创建独立的承诺链。然后()处理程序未返回的处理程序。这通常是一个错误,除非在极少数情况下发生火灾,忘记没有错误处理。

You generally do not want to create independent promise chains within a .then() handler that are not returned from the handler. This is usually a bug except in rare cases of fire and forget without error handling.

逐行Analsysis

所以,这里是对你的代码的分析。我添加了行号并清理了缩进以便于讨论:

So, here's an analysis of your code. I added line numbers and cleaned up the indentation to make it easier to discuss:

1     Promise.resolve('A').then(function (a) {
2         console.log(2, a);
3         return 'B';
4     }).then(function (a) {
5         Promise.resolve('C').then(function (a) {
6             console.log(7, a);
7         }).then(function (a) {
8             console.log(8, a);
9         });
10        console.log(3, a);
11        return a;
12    }).then(function (a) {
13        Promise.resolve('D').then(function (a) {
14            console.log(9, a);
15        }).then(function (a) {
16            console.log(10, a);
17        });
18        console.log(4, a);
19    }).then(function (a) {
20        console.log(5, a);
21    });
22   
23    console.log(1);
24    
25    setTimeout(function () {
26        console.log(6)
27    }, 0);

第1行启动承诺链并附上 .then()处理程序。由于 Promise.resolve()立即解析,Promise库将安排第一个 .then()处理程序运行这个Javascript的线程完成。在Promises / A +兼容的promise库中,所有 .then()处理程序在当前执行线程完成后以及JS返回事件循环时异步调用。这意味着此线程中的任何其他同步代码(例如 console.log(1)将在接下来运行,这就是您所看到的。

Line 1 starts a promise chain and attached a .then() handler to it. Since Promise.resolve() resolves immediately, the Promise library will schedule the first .then() handler to run after this thread of Javascript finishes. In Promises/A+ compatible promise libraries, all .then() handlers are called asynchronously after the current thread of execution finishes and when JS goes back to the event loop. This means that any other synchronous code in this thread such as your console.log(1) will run next which is what you see.

所有其他 .then()处理程序位于顶层(第4,12,19行)后的第一个处理程序一,只有在第一个轮到之后才能运行。它们基本上排在这一点。

All the other .then() handlers at the top level (lines 4, 12, 19) chain after the first one and will run only after the first one gets its turn. They are essentially queued at this point.

由于 setTimeout()也在这个初始执行线程中,它被运行,因此计划了一个计时器。

Since the setTimeout() is also in this initial thread of execution, it is run and thus a timer is scheduled.

这是同步执行的结束。现在,JS引擎开始运行在事件队列中安排的事情。

That is the end of the synchronous execution. Now, the JS engine starts running things that are scheduled in the event queue.

据我所知,无法保证首先出现 setTimeout(fn,0)或者一个 .then()处理程序,它们都被安排在这个执行线程之后运行。 .then()处理程序被认为是微任务,因此我们在 setTimeout()。但是,如果您需要特定订单,那么您应该编写保证订单的代码,而不是依赖于此实现细节。

As far as I know, there is no guarantee which comes first a setTimeout(fn, 0) or a .then() handler that are both scheduled to run right after this thread of execution. .then() handlers are considered "micro-tasks" so it does not surprise me that they run first before the setTimeout(). But, if you need a particular order, then you should write code that guarantees an order rather than rely on this implementation detail.

无论如何, .then() 第1行上定义的处理程序接下来运行。因此,您可以从 console.log(2,a)中看到输出 2A

Anyway, the .then() handler defined on line 1 runs next. Thus you see the output 2 "A" from that console.log(2, a).

接下来,由于前一个 .then()处理程序返回了一个普通值,该承诺被视为已解决,因此 .then()第4行上运行的处理程序。这里是您创建另一个独立承诺链并引入通常是错误的行为的地方。

Next, since the previous .then() handler returned a plain value, that promise is considered resolved so the .then() handler defined on line 4 runs. Here's where you're creating another independent promise chain and introducing a behavior that is usually a bug.

第5行,创建一个新的Promise链。它解析了初始承诺,然后安排两个 .then()处理程序在当前执行线程完成时运行。在当前执行的线程是第10行的 console.log(3,a),这就是为什么你看到下一个。然后,这个执行线程结束,然后返回调度程序,看看接下来要运行什么。

Line 5, creates a new Promise chain. It resolves that initial promise and then schedules two .then() handlers to run when the current thread of execution is done. In that current thread of execution is the console.log(3, a) on line 10 so that's why you see that next. Then, this thread of execution finishes and it goes back to the scheduler to see what to run next.

我们现在队列中有几个 .then()处理程序,等待下一次运行。我们刚刚在第5行安排了一个,第12行的更高级链中有下一个。如果你在第5行上完成了这个:

We now have several .then() handlers in the queue waiting to run next. There's the one we just scheduled on line 5 and there's the next one in the higher level chain on line 12. If you had done this on line 5:

return Promise.resolve.then(...)



然后你会将这些承诺联系在一起,它们将按顺序协调。但是,通过不返回承诺值,您开始了一个全新的承诺链,而不是与外部的更高级别承诺协调。在您的特定情况下,promise调度程序决定接下来运行更深层嵌套的 .then()处理程序。我真的不知道这是通过规范,按惯例还是只是一个承诺引擎与另一个承诺引擎的实现细节。我会说,如果订单对你很重要,那么你应该通过按特定顺序链接承诺来强制执行订单,而不是依靠谁赢得竞争首先运行。

then you would have linked these promises together and they would be coordinated in sequence. But, by not returning the promise value, you started a whole new promise chain that is not coordinated with the outer, higher level promise. In your particular case, the promise scheduler decides to run the more deeply nested .then() handler next. I don't honestly know if this is by specification, by convention or just an implementation detail of one promise engine vs. the other. I'd say that if the order is critical to you, then you should force an order by linking promises in a specific order rather than rely on who wins the race to run first.

无论如何,在你的情况下,这是一个调度竞赛,你正在运行的引擎决定运行内部 .then()处理程序,该处理程序在第5行定义,因此你看到第6行上指定的 7C。然后它返回任何内容,因此该promise的解析值变为 undefined

Anyway, in your case, it's a scheduling race and the engine you are running decides to run the inner .then() handler that's defined on line 5 next and thus you see the 7 "C" specified on line 6. It then returns nothing so the resolved value of this promise becomes undefined.

返回调度程序,它运行第12行上的 .then()处理程序。这又是 .then()处理程序和第7行上的处理程序之间的竞争,它也在等待运行。我不知道为什么它在这里选择一个而不是说它可能是不确定的或因每个承诺引擎而异,因为代码没有指定顺序。无论如何,第12行中的 .then()处理程序开始运行。这再次创建了一个新的独立或不同步的承诺链线。它再次调度 .then()处理程序,然后从同步代码中获取 4B .then()处理程序。所有同步代码都在该处理程序中完成,所以现在它会返回到调度程序以进行下一个任务。

Back in the scheduler, it runs the .then() handler on line 12. This is again a race between that .then() handler and the one on line 7 which is also waiting to run. I don't know why it picks one over the other here other than to say it may be indeterminate or vary per promise engine because the order is not specified by the code. In any case, the .then() handler in line 12 starts to run. That again creates a new independent or unsynchronized promise chain line the previous one. It schedules a .then() handler again and then you get the 4 "B" from the synchronous code in that .then() handler. All synchronous code is done in that handler so now, it goes back to the scheduler for the next task.

回到调度程序中,它决定运行 .then() 第7行上的处理程序,你得到 8 undefined 。承诺存在 undefined 因为该链中的前一个 .then()处理程序没有返回任何内容,因此返回value是 undefined ,因此这是该点的promise链的解析值。

Back in the scheduler, it decides to run the .then() handler on line 7 and you get 8 undefined. The promise there is undefined because the previous .then() handler in that chain did not return anything, thus its return value was undefined, thus that is the resolved value of the promise chain at that point.

此时,到目前为止的输出是:

At this point, the output so far is:

1
2 "A"
3 "B"
7 "C"
4 "B"
8 undefined

再次,完成所有同步代码,使其再次返回调度程序,并决定运行第13行上定义的 .then()处理程序。运行,你得到输出 9D,然后它再次回到调度程序。

Again, all synchronous code is done so it goes back to the scheduler again and it decides to run the .then() handler defined on line 13. That runs and you get the output 9 "D" and then it goes back to the scheduler again.

一致使用先前嵌套的 Promise.resolve()链,计划选择运行下一个外部 .then()第19行上定义的处理程序。它运行,你得到输出 5 undefined 。它再次是 undefined ,因为该链中的前一个 .then()处理程序没有返回值,因此已解决承诺的价值是未定义

Consistent with the previously nested Promise.resolve() chain, the the schedule chooses to run the next outer .then() handler defined on line 19. It runs and you get the output 5 undefined. It is again undefined because the previous .then() handler in that chain did not return a value, thus the resolved value of the promise was undefined.

至此,到目前为止的输出是:

As this point, the output so far is:

1
2 "A"
3 "B"
7 "C"
4 "B"
8 undefined
9 "D"
5 undefined

此时,只有一个 .then()处理程序计划运行,因此它运行在第15行上定义的处理程序,你得到输出 10 undefined next。

At this point, there is only one .then() handler scheduled to be run so it runs the one defined on line 15 and you get the output 10 undefined next.

然后,最后, setTimeout()开始运行,最终输出为:

Then, lastly, the setTimeout() gets to run and the final output is:

1
2 "A"
3 "B"
7 "C"
4 "B"
8 undefined
9 "D"
5 undefined
10 undefined
6

如果有人试图准确预测这将会发生的订单,那么就会有两个主要问题。

If one were to try to predict exactly the order this would run in, then there would be two main questions.


  1. 待处理的 .then()处理程序如何优先处理与 setTimeout()还处于待处理状态的调用。

  1. How are pending .then() handlers prioritized vs. setTimeout() calls that are also pending.

承诺引擎如何决定优先处理所有等待的多个 .then()处理程序跑。按照此代码的结果,它不是FIFO。

How does the promise engine decide to prioritize multiple .then() handlers that are all waiting to run. Per your results with this code it is not FIFO.

对于第一个问题,我不知道是否这样在promise引擎/ JS引擎中是每个规范或只是一个实现选择,但是您报告的实现似乎在任何<$之前优先处理所有挂起的 .then()处理程序c $ c> setTimeout()来电。您的情况有点奇怪,因为除了指定 .then()处理程序之外,您没有实际的异步API调用。如果您有任何异步操作实际上在此承诺链的开头实际执行,那么您的 setTimeout()将在之前执行.then()真实异步操作的处理程序,因为真正的异步操作需要实际执行时间。所以,这是一个人为的例子,并不是真正代码的通常设计案例。

For the first question, I don't know if this is per specification or just an implementation choice here in the promise engine/JS engine, but the implementation you reported on appears to prioritize all pending .then() handlers before any setTimeout() calls. Your case is a bit of an odd one because you have no actual async API calls other than specifying .then() handlers. If you had any async operation that actually took any real time to execute at the start of this promise chain, then your setTimeout() would execute before the .then() handler on the real async operation just because the real async operation takes actual time to execute. So, this is a bit of a contrived example and is not the usual design case for real code.

对于第二个问题,我看过一些讨论如何讨论挂起 .then()应优先处理不同嵌套级别的处理程序。我不知道该讨论是否曾在规范中得到解决。我更喜欢以一种细节级别对我来说无关紧要的方式进行编码。如果我关心我的异步操作的顺序,那么我链接我的promise链来控制顺序,这个级别的实现细节不会以任何方式影响我。如果我不关心订单,那么我不关心订单,所以实施细节的水平不会影响我。即使这是在某种规范中,看起来这种细节类型不应该被许多不同的实现(不同的浏览器,不同的承诺引擎)所信任,除非你已经在你要运行的任何地方测试过它。所以,当你有不同步的保证链时,我建议不要依赖特定的执行顺序。

For the second question, I've seen some discussion that discusses how pending .then() handlers at different levels of nesting should be prioritized. I don't know if that discussion was ever resolved in a specification or not. I prefer to code in a way that that level of detail does not matter to me. If I care about the order of my async operations, then I link my promise chains to control the order and this level of implementation detail does not affect me in any way. If I don't care about the order, then I don't care about the order so again that level of implementation detail does not affect me. Even if this was in some specification, it seems like the type of detail that should not be trusted across many different implementations (different browsers, different promise engines) unless you had tested it everywhere you were going to run. So, I'd recommend not relying on a specific order of execution when you have unsynchronized promise chains.

你可以做通过链接所有你的承诺链来确定订单100%(返回内部承诺,以便它们链接到父链):

You could make the order 100% determinate by just linking all your promise chains like this (returning inner promises so they are linked into the parent chain):

Promise.resolve('A').then(function (a) {
    console.log(2, a);
    return 'B';
}).then(function (a) {
    var p =  Promise.resolve('C').then(function (a) {
        console.log(7, a);
    }).then(function (a) {
        console.log(8, a);
    });
    console.log(3, a);
    // return this promise to chain to the parent promise
    return p;
}).then(function (a) {
    var p = Promise.resolve('D').then(function (a) {
        console.log(9, a);
    }).then(function (a) {
        console.log(10, a);
    });
    console.log(4, a);
    // return this promise to chain to the parent promise
    return p;
}).then(function (a) {
    console.log(5, a);
});

console.log(1);

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

这会在Chrome中提供以下输出:

This gives the following output in Chrome:

1
2 "A"
3 "B"
7 "C"
8 undefined
4 undefined
9 "D"
10 undefined
5 undefined
6

并且,由于承诺全部链接在一起,承诺顺序全部由代码定义。作为一个实现细节留下的唯一的东西是 setTimeout()的时间,在你的例子中,在所有挂起的之后,它是最后一个.then ()处理程序。

And, since the promise have all been chained together, the promise order is all defined by the code. The only thing left as an implementation detail is the timing of the setTimeout() which, as in your example, comes last, after all pending .then() handlers.

修改:

在检查承诺/ A +规范后,我们发现:

Upon examination of the Promises/A+ specification, we find this:


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

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

....

3.1此处平台代码表示引擎,环境和承诺实现代码。实际上,这个要求确保
onFulfilled和onRejected异步执行,之后调用
循环,然后调用新堆栈。这可以是
,可以使用宏任务机制(如setTimeout或
setImmediate)实现,也可以使用微任务机制(如
MutationObserver或process.nextTick)实现。由于promise实现
被认为是平台代码,它本身可能包含一个任务调度
队列或调用处理程序的trampoline。

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.

这表示 .then()处理程序必须在调用堆栈返回平台代码后异步执行,但将其完全留给实现无论是使用像 setTimeout()这样的宏任务还是像这样的微任务process.nextTick()。因此,根据此规范,它不是确定的,不应该依赖。

This says that .then() handlers must execute asynchronously after the call stack returns to platform code, but leaves it entirely to the implementation how exactly to do that whether it's done with a macro-task like setTimeout() or micro-task like process.nextTick(). So, per this specification, it is not determinate and should not be relied upon.

我找不到有关宏任务,微任务或承诺时间的信息 .then() ES6规范中与 setTimeout()相关的处理程序。这可能并不奇怪,因为 setTimeout()本身不是ES6规范的一部分(它是主机环境函数,而不是语言特性)。

I find no information about macro-tasks, micro-tasks or the timing of promise .then() handlers in relation to setTimeout() in the ES6 specification. This is perhaps not surprising since setTimeout() itself is not part of the ES6 specification (it is a host environment function, not a language feature).

我还没有找到任何规范来支持这个问题,但这个问题的答案是事件循环上下文中微任务和宏任务之间的区别解释了在具有宏任务和微任务的浏览器中,事物往往会如何工作。

I haven't found any specifications to back this up, but the answers to this question Difference between microtask and macrotask within an event loop context explain how things tend to work in browsers with macro-tasks and micro-tasks.

仅供参考,如果您想了解有关微任务和宏任务的更多信息,请参阅以下有关该主题的有趣参考文章:任务,微任务,队列和日程安排

FYI, if you want more info on micro-tasks and macro-tasks, here's an interesting reference article on the topic: Tasks, microtasks, queues and schedules.

这篇关于在javascript承诺中执行的顺序是什么的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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