异步机制整理
起因
先看最近遇到的一段代码:
1 |
|
这段代码的输出如上所示,虽然事后想明白了,但是当时还是犯了很蠢的问题,所以整理了一下有关于 js 的异步原理的整理。
什么是异步
js 作为单线程语言,如果同步执行则会造成非常严重的性能问题,所以在 js 设计中使用了异步机制,异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
异步执行机制:
所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,在”任务队列”之中放置一个事件。
一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
主线程不断重复
Event Loop
主线程从任务队列中不断读取事件,这个过程是循环不断的,此过程被称为 Event Loop
在主线程运行时候,产生了堆和栈,其中,栈的代码调用了外部的 API,在任务队列中不断地加入时间,当栈中的代码执行完毕后,主线程会取执行任务队列,一次调用其中的回调函数,执行栈的同步任务总是会在异步任务前执行
宏任务与微任务
对于广义来说,可以定义同步任务和异步任务,但是对于异步还可以有更精细的划分,其中分为宏任务与微任务
宏任务分类:定时器,整体 script 代码
微任务定义:Promise(then 的回调),process.nextTick()
对于同时存在宏任务和微任务的等待队列,首先会执行微任务,同时这也解释了开头那个问题中的定时器被清理的结果。
画个图来表示一下:
对于定时器中的函数,作为宏任务,他的回调顺序是在微任务的回调之后的,这也就造成了定时器的代码无法顺利执行的结果。
Node 中的执行机制
Node.js 也是单线程的 Event Loop,但是它的运行机制不同于浏览器环境。
- V8 引擎解析 JS 脚本
- 调用 Node API
- libuv 库执行 Node API,同时,将不同任务分配给不同的线程,形成一个时间循环,以异步方式将任务执行结果返回给 V8 引擎
- V8 引擎将结果返还给用户
其中,Node 中提供了一些特殊的与任务队列有关的 API:process.nextTick 和 setImmediate
process.nextTick 方法可以在当前执行栈的尾部,下一次读取任务队列前触发回调
setImmediate 方法则是在当前任务队列的尾部添加事件
1 |
|
如果有多个 process.nextTick 语句(不管它们是否嵌套),将全部在当前”执行栈”执行。
1 |
|
我们由此得到了 process.nextTick 和 setImmediate 的一个重要区别:多个 process.nextTick 语句总是在当前”执行栈”一次执行完,多个 setImmediate 可能则需要多次 loop 才能执行完。事实上,这正是 Node.js 10.0 版添加 setImmediate 方法的原因,否则像下面这样的递归调用 process.nextTick,将会没完没了,主线程根本不会去读取”事件队列”!
- 本文作者:herin
- 本文链接:https://kilicmu.github.io/2020/03/07/%E5%BC%82%E6%AD%A5%E6%9C%BA%E5%88%B6%E6%95%B4%E7%90%86/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!