聊聊JavaScript异步中的macrotask和microtask
前言
首先来看一个JavaScript的代码片段:
console.log(1); setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3) }); }, 0); new Promise((resolve, reject) => { console.log(4) resolve(5) }).then((data) => { console.log(data); }) setTimeout(() => { console.log(6); }, 0) console.log(7);
如果你能知道正确的答案,那么后续的内容可以略过了;如果不能建议看看下面有关js异步的内容,百利无一害,😁😁。
任务队列
js的一大特点是单线程,即同一个时间只能做一件事,这样设计主要与其作为浏览器脚本语言有关,js主要用途是用户交互以及操作dom,这决定其是单线程设计,否则会带来复杂的同步问题。比如一个线程删除一个节点,而另一个线程要操作该节点,浏览器不知以哪个线程为准。
单线程意味着任务需要排队,如果前一个任务耗时长,那么就会阻塞后续任务的执行。为此js出现了同步和异步任务,二者都需要在主线程执行栈中执行;其中异步任务需要进入任务队列(task queue)进行排队,其具体运行机制如下:
-
同步任务在主线程上执行,形成一个执行栈
-
js会将主线程执行栈中的异步任务置于任务队列排队
-
一旦主线程执行栈同步任务执行完毕处于空闲状态时,就会将任务队列中任务入栈开始执行
还是先来看一个js片段:
console.log('script start') setTimeout(function() { console.log('timeout') }, 0) console.log('script end')
这段代码在进入主线程执行时,当执行到setTimeout时会将其放置到异步任务队列中,即使设置时间为0也不会马上执行,必须等到主线程执行栈空闲时(执行完console.log('script end')语句后)才会读取异步队列的任务执行。
macrotask与microtask
二者任务都会被放置于任务队列中等待某个时机被主线程入栈执行,其实任务队列分为宏任务队列和微任务队列,其中放置的分别为宏任务和微任务。
-
macrotask(宏任务) 在浏览器端,其可以理解为该任务执行完后,在下一个macrotask执行开始前,浏览器可以进行页面渲染。触发macrotask任务的操作包括:
-
scrip(整体代码)
-
setTimeout、setInterval、setImmediate(Node环境)
-
I/O、UI交互事件
-
postMessage、MessageChannel
-
-
microtask(微任务)可以理解为在macrotask任务执行后,页面渲染前立即执行的任务。触发microtask任务的操作包括:
-
Promise.then
-
MutationObserver
-
process.nextTick(Node环境)
-
下面通过例子来看看二者的不同:
console.log('script start'); setTimeout(function() { console.log('timeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');