使用 queueMicrotask 来执行微任务

更新日期: 2019-09-07阅读: 2.1k标签: 任务

写在前面

写这篇文章的原因是因为,这几天在看 core-js 的源码,然后发现了 queueMicrotask 的实现。由于之前做的项目,对于微任务的执行需求,一般是使用 asap 这个库来完成的,如果没有使用这个库的话,简易版本可以通过 Promise.resolve() 来代替,并没有接触过过这个 api,所以就抽时间研究一下。


兼容性

一般看这种偏 web 标准的新的 api,肯定上来要先看兼容性的,我去 caniuse 查了一下,wtf? 竟然搜索无结果。(详见 issue

然后只能去 MDN 来看一下了,大概是下图这个样子:


可以发现还是比较新的 api,如果要在项目中直接使用的话,还是建议导入 polyfill 或者使用 asap 这个库来实现类似的需求。


为什么我们需要这个 api?

从微任务本身的概念来说的话,就是当我们期望某段代码,不阻塞当前执行的同步代码,同时又期望它尽可能快地执行时,我们就需要它(这里不再赘述微任务的概念,可以参考这篇文章)。

一般情况下,如果是编写业务代码,我觉的很少会遇到这样的需求,唯一能想到的情况可能存在于一些对即时反馈有性能要求的场景,比如搜索,当输入关键字后发送异步请求获取搜索信息之后,我们可能会在前端对搜索结果进行一些处理,比如排序或者分组,但是这些操作可能不是优先级最高的任务,但它们又比较耗时(比如排序),因此我们可能期望推迟它们的执行,但又期望它们尽可能早地执行。

在阅读一些著名框架或者工具库的过程中,我发现很多情况下作者都会遇到这个需求,一般都通过 process.nextTick 或者 Promise.resolve 来解决。


它和 setTimeout 的区别?

本质上的区别应该在它们的执行时机上,而执行时机上的区别,本质上就是微任务和宏任务的区别。可以直接打开控制台运行一下以下的代码:

setTimeout(() => {
    console.log('setTimeout');
}, 0);

queueMicrotask(() => {
    console.log('queueMicrotask');
}); 

运行结果不出意外应该是:

queueMicrotask
setTimeout

如果你熟悉 nodejs 的话,应该和 process.nextTick 是类似的。


使用其他方式进行模拟所带来的问题?

这也是我一开始脑海中出现的问题,就是既然我们已经可以通过别的方式来模拟微任务的执行,我们还需要这个 api 干什么?比如,通过下面的代码:

setTimeout(() => {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(() => {
    console.log('queueMicrotask');
}); 

会得到和上面代码一样的运行结果。

这里引用 Explainer: queueMicrotask 的一些观点来进行阐述:

我们应当使用底层 api 来直接完成类似的功能,而非用顶层 api 进行模拟

模拟过程中,对于异常情况,会造成一些困扰,比如 Promise.resolve 会将异常转化为一个 rejected 的 Promise

模拟过程中,会创建额外的对象(造成一定意义上的浪费),比如 Promise.resolve 会返回一个 Promise 实例对象,而直接 queueMicrotask 则不会

除了微任务,其他类型的异步任务都有对应的 api 可供使用,比如宏任务、RAF

继上一点的基础上,语义性会更好,同时帮助开发者理解这些不同异步任务之间的区别

setTimeout(callback, 0) - 宏任务
requestAnimationFrame(callback) - RAF
queueMicrotask(callback) - 微任务


潜在问题

由于它是一个用于指派微任务的底层 api,我们很可能会在其中无限制地指派微任务到其队列之中,这样做的效果就是,浏览器的微任务队列始终处于非空状态,这将导致控制权始终无法交还给浏览器进行下一次事件循环,然后它就卡死了。

你可以执行下面的代码来体验这个现象:

function infiniteEnqueue(fn) {
    queueMicrotask(() => infiniteEnqueue(fn))
}

infiniteEnqueue(()=>{})

执行这段代码会使浏览器当前的 tab 卡死,请慎用,建议先打开浏览器提供的进程管理窗口以供强制关闭卡死窗口。


关于 polyfill 的不同实现

这里简单阐述 MDN 上的和 core-js 中的模拟方案。

MDN

MDN 上的 polyfill 实现比较简单粗暴,其实和直接调用 Promise.resolve 没什么区别,只是会在 .catch 中捕获错误之后再抛出。

core-js

相比较 MDN 的实现,core-js 会复杂一些,它同时考虑了 nodejs 和 browser 两种情况,同时利用链表数据结构来模拟微任务队列的执行单元,同时实现了一个 flush 方法表示执行全部的微任务单元。

还实现了一个 notify 方法,该方法会根据具体的 js 运行时环境以及 api 的支持情况,分别尝试使用 process.nextTick、MutationObserver 和 Promise.resolve 以及最基本的宏任务 api 来执行 flush 方法,变相模拟微任务的执行过程。

原文:https://segmentfault.com/a/1190000020332724

链接: https://www.fly63.com/article/detial/5213

PHP实现执行定时任务的几种思路详解

PHP定时任务是一个非常有意思的东西,虽然说实话,用系统的php.exe去直接执行php文件的效率更高,但是对于很多普通站长而言,虚拟主机是无法做到直接php执行原生程序的。本文仅提供一些解决的思路

js中特殊的宏任务

目前只有IE10+和NodeJS支持该API。立即触发回调函数,使其进入宏任务队列(macro task queue),比setTimout(fn, 0)的执行顺序要快,性能也更高。因为setTimeout(fn,0)实质上会有4ms的延迟。

Node.js实现定时任务

在本文中,我们将研究如何在 Node 程序中创建和使用 Cron 作业。为此我们将创建一个简单的程序,该应用程序会自动从服务器中删除自动生成的 error.log 文件。 Cron 作业的另一个优点是

JS微任务 宏任务,Promise、setTimeout、setImmediate运行顺序实测

虽然理论上应当先运行Promise,再运行setTimeout。但是由于历史版本或使用polyfill,使得Promise未必优先运行。setImmediate未必比setTimeout早运行

关于宏任务、微任务和事件循环

为什么会是这样的输出顺序呢?这就要提到事件循环、宏任务和微任务的概念了。众所周知,JavaScript是一个单线程的语言,单线程意味着代码会自上而下依次执行,如果有一个耗时的操作,那么页面就会卡死,基于此,便有了异步的概念

搞懂JS的事件循环(Event Loop)和宏任务/微任务

首先大家都知道JS是一门单线程的语言,所有的任务都是在一个线程上完成的。而我们知道,有一些像I/O,网络请求等等的操作可能会特别耗时,如果程序使用同步模式等到任务返回再继续执行,就会使得整个任务的执行特别缓慢

如何在 JavaScript 中使用宏

在语言当中,宏常见用途有实现 DSL 。通过宏,开发者可以自定义一些语言的格式,比如实现 JSX 语法。在 WASM 已经实现的今天,用其他语言来写网页其实并不是没有可能

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!