天天看点

jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

喜欢排队吧,它能保护你的时间和精力 - 排队纪律维护员Event Loop
jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

Promise和事件循环概览图

请注意上面这张图,Promise和事件循环的那些事,将在这个图上缓缓展开。

微任务和(宏)任务

好了,(经过上一节对Promise的理解)现在我们对如何创建Promise以及如何从Promise中获取值多了一些了解。让我们在script中添加更多的代码,然后再次运行它:

jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

等等,发生了什么?!

首先,输出了"Start!"。好吧,显而易见的:console.log('Start!') 就在第一行!但是,输出的第二个值是"End!"而不是Promise的值!只有在"End!"输出后,Promise的值才被输出。这是怎么回事?

我们终于见识到Promise的真正力量!虽然JavaScript是单线程的,但是我们可以使用Promise添加异步行为!

但是稍等一下,我们以前不是见过了吗?在JavaScript事件循环中(「翻译」JavaScript的可视化学习之一:事件循环),我们不也可以使用浏览器的本地方法(如setTimeout)来创建某种异步行为吗?

没错!但是,在事件循环中,实际上有两种类型的队列:(宏)任务队列(或简称为任务队列):(macro) task queue 和微任务队列:microtask queue。(宏)任务队列用于(宏)任务,微任务队列用于微任务。

那么什么是(宏)任务,什么是微任务呢?下面表格罗列了一些最常用的!

(Macro)task:

setTimeout

setInterval

setImmediate

Microtask:

process.nextTick

Promise callback

queueMicrotask

啊,我们在微任务列表中看到了Promise!当Promise处理并调用其then()、catch()或finally()方法时,该方法内的回调将添加到微任务队列中!这意味着then()、catch()或finally()方法中的回调不会立即执行,这实际上是在我们的JavaScript代码中添加了一些异步行为!

那么then()、catch()或finally()回调函数在什么时候执行呢?事件循环为任务设定了不同的优先级:

1、当前在调用堆栈中的所有函数被执行,当它们返回一个值时,它们将从堆栈中弹出。

2、当调用堆栈为空时,所有排队的微任务将一个接一个的弹出到调用堆栈中,并执行!(微任务本身也可以返回新的微任务,实际创建了一个无限的微任务循环)

3、如果调用堆栈和微任务队列都为空,则事件循环将检查(宏)任务队列中是否还有任务。如果有,任务被弹出到调用堆栈,执行,然后弹出调用堆栈!

让我们看一个简单的例子,简单地使用:

Task1: 一个立即添加到调用堆栈中的函数,例如在代码中立即调用它。

Task2, Task3, Task4: 微任务,例如一个Promise的then回调,或使用queueMicrotask添加的任务。

Task5, Task6: (宏)任务,例如setTimeout或setImmediate回调。

jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

首先,Task1返回一个值并从调用堆栈中弹出。然后,引擎检查微任务队列中排队的任务。一旦所有任务都被放入调用堆栈并最终弹出,引擎将检查(宏)任务队列中的任务,任务将弹出到调用堆栈中,并在它们返回值时弹出。

好吧,好吧,粉色盒子够多了。让我们用一些真正的代码来使用它!

console.log('Start!')setTimeout(() => {console.log('Timeout!')}, 0)Promise.resolve('Promise').then(res => console.log(res))console.log('End!')
           

在这段代码中,我们有宏任务setTimeout和微任务Promise的 then()回调。定位到setTimeout函数的行,让我们一步一步地运行这段代码,看看会输出了什么!

小提示 - 在下面的示例中,我展示了如下方法console.log,setTimeout和Promise.resolve,他们被添加到调用堆栈中。它们是内部方法,实际上不会出现在堆栈跟踪中 — 因此,如果您正在使用调试器并且在任何地方都看不到它们,请不要担心!这么做只是为了让解释这个概念更容易,而不需要添加一堆示例代码。

在第一行,引擎遇到console.log()方法。它被添加到调用堆栈中,然后输出"Start!"到控制台。方法从调用堆栈中弹出,引擎继续运行。

jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

引擎遇到setTimeout方法,该方法被弹出到调用堆栈中。setTimeout方法是浏览器的本地方法:它的回调函数(() => console.log('In timeout')) 将被添加到Web API中,直到计时器完成。虽然我们为计时器提供了值 0,但是回调仍然会首先被推送到Web API,之后它会被添加到(宏)任务队列中:setTimeout是一个宏任务!

jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

引擎遇到Promise.resolve()方法。这个Promise.resolve()方法被添加到调用堆栈中,之后处理并返回"Promise!"。然后它的then回调函数被添加到微任务队列中。

jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

引擎遇到console.log()方法。它会立即添加到调用堆栈中,然后输出"End!"到控制台,从调用堆栈中弹出,引擎继续运行。

jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

引擎发现调用堆栈现在是空的。由于调用堆栈是空的,它将检查微任务队列中是否有排队的任务!是的,Promise的then回调正在等待!它被弹出到调用堆栈中,然后输出Promise的值:在这里是字符串"promise!"。

jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

引擎发现调用堆栈是空的,因此它将再次检查微任务队列,以查看是否有排队的任务。不,微任务队列都是空的。

是时候检查(宏)任务队列了:setTimeout回调仍在那里等待!setTimeout回调被弹出到调用堆栈。回调函数返回console.log方法,该方法输出字符串“In timeout!”。setTimeout回调从调用堆栈中弹出。

jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

终于,一切都完成了!现在看来我们之前看到的输出结果并不是那么出乎意料。

翻译来源:https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke