行业知识
向微队列添加任务的四种方式
向微队列添加任务的四种方式
关于���任务,微队列,事件循环,可参考:深入:微任务与 Javascript 运行时环境 - Web API 接口参考 | MDN (mozilla.org)
先说答案, 四种方法:
- Promise.resolve().then();
- MutationObserver
- process.nextTick();
- queueMicrotask()
Promise.resolve().then()
查看ECMA-262关于then方法的描述:
可知then方法会在promise状态为fulfilled或rejected时将对应的任务(onFulfilled和onRejected)放到任务队列中,然后注意到MDN在一篇关于微任务的文章中提到:
JavaScript 中的 promise 和 Mutation Observer API 都使用微任务队列去运行它们的回调函数
于是我们可以知道then方法的回调将会被添加到微队列中执行, 若查看V8引擎源码可进一步佐证这一点。
这样一来,我们就可以用以下代码向微队列中添加一个任务:
const task = () => { console.log("do somthing");}Promise.resolve().then(task); 进一步, 根据ECMA-262的描述(上图红框部分), 当promise状态为rejected时,onRejected也会被添加到微队列, 于是以下代码也可以达到相同的效果:
//以下两种方法等价Promise.reject().then(null, task);Promise.reject().catch(task);
验证一下, 执行以下代码, 发现打印顺序符合预期:
setTimeout(() => { console.log("timeout");}, 0);Promise.resolve().then(() => { console.log("promise resolve");})Promise.reject().then(null, () => { console.log("promise reject");});Promise.reject().catch(() => { console.log("promise catch");});console.log("同步");//打印结果:// 同步// promise resolve// promise reject// promise catch// timeout MutationObserver API
同样根据上文提到的MDN的描述, 使用MutationObserver也可以达到向微队列中添加任务的目的, 结合MutationObserver的用法可以封装一个通用的函数:
function enqueueMicroTaskByMutationObserver(task) { if (typeof task != "function") { throw "task必须为函数"; } let m = new MutationObserver(task); let p = document.createElement("p"); m.observe(p, { childList: true }); p.innerText = "1"; } 注意: nodejs环境不支持MutationObserver API, 因此这个方法只能在浏览器环境中使用
在html中运行以下代码验证, 打印顺序符合预期:
Document function enqueueMicroTaskByMutationObserver(task) { if (typeof task != "function") { throw "task必须为函数"; } let m = new MutationObserver(task); let p = document.createElement("p"); m.observe(p, { childList: true }); p.innerText = "1"; } setTimeout(() => { console.log("time out"); }, 0); enqueueMicroTaskByMutationObserver(() => { console.log("MutationObserver"); }) process.nextTick()
与MutationObserver API相反,这个方法只能用于Node环境,根据Node文档的描述:
process.nextTick() 将 callback 添加到 “下一个滴答队列”。在 JavaScript 堆栈上的当前操作运行完成之后,且在允许事件循环继续之前,此队列将被完全排空。
暂时可将"滴答队列"简单理解为微队列,实际上,"滴答队列"的优先级似乎比微队列更高, 观察以下代码:
setTimeout(() => { console.log("timeout");}, 0);Promise.resolve().then(() => { console.log("promise resolve");})process.nextTick(() => { console.log("tick");})console.log("同步"); 发现无论执行多少次, "tick"总是先于"promise resolve"打印,于是推测"滴答队列"的优先级似乎比微队列更高.
queueMicrotask
在不考虑兼容性的情况下, 应当首选这个方法, 因为这个方法就是专门用来做将微任务加入队列这件事的, 参考MDN:queueMicrotask() - Web API 接口参考 | MDN (mozilla.org)
同时, queueMicrotask在浏览器和node环境中都可以使用且用起来也是最简单的, 用法如下:
queueMicrotask(() => { console.log("do somthing");}) 如有错漏,恳请指出