JavaScript中的异步 macrotask 和 microtask
看过很多setTimeout、Promise执行顺序的面试题,一直不明白为啥都是异步操作,Promise就牛×些呢?直到了解了macrotask和micromask才恍然大悟...
先来一道面试题助助兴:
setTimeout(()=>{ console.log('A'); },0); var obj={ func: function () { setTimeout(function () { console.log('B') },0); return new Promise(function (resolve) { console.log('C'); resolve(); }) } }; obj.func().then(function () { console.log('D') }); console.log('E'); // 结果:C、E、D、A、B
我们都知道JavaScript是一门单线程的语言,这也就意味着 JS 无法进行多线程编程,但是 JS 当中却有着无处不在的异步概念 。要完全理解异步,就需要了解 JS 的运行核心——事件队列(Event Loop)。实际上,JS代码执行都处于事件循环里。事件循环发现有异步事件发生,就把这个任务放到事件队伍中。
事件队列是一个存储着待执行任务的队列,直白点说就是:我们把每一次的异步操作(setTimeout、onclick、oninput事件、Promise...)当做一个异步任务,每进行一次异步操作,就把这个异步任务放入到异步事件的队列中,直到主线程任务执行完毕,然后开始异步的事件队列里的任务按顺序执行。
Macrotasks和Microtasks
Macrotasks和Microtasks 都属于上述的异步任务中的一种,他们分别有如下API:
macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks: process.nextTick, Promise, MutationObserver
异步任务队列分为 macrotasks 和 microtasks, 在每一次事件循环中,macrotask只会提取一个执行,而microtask会一直提取,直到microsoft队列为空为止。也就是说如果某个microtask任务被推入到执行中,那么当主线程任务执行完成后,会循环调用该队列任务中的下一个任务来执行,直到该任务队列到最后一个任务为止(microtasks优于macrotasks执行)。而事件循环每次只会入栈一个macrotask,主线程执行完成该任务后又会检查microtasks队列并完成里面的所有任务后再执行macrotask的任务。
弄清楚了这条规则后,再来分析上面的面试题就小菜一碟了。
参考文章:
https://juejin.im/post/5bac87b6f265da0a906f78d8
http://ju.outofmemory.cn/entry/349456