理解Promise规范和异步任务
Promise API在前端开发中经常出现,它用于解决异步流程的控制。虽然,我们或多或少都用过,但是可能对它的理解,还不够深刻。 在ES中 Promise 是一种典型的微任务,而setTimeout 、setInterval 是典型的宏任务,二者交错出现在代码逻辑中。 如果对两者的执行时机,理解不到位,很容易搞不懂代码的执行顺序,或者对代码的运行结果一头雾水。
Promise出现背景
由于JS是单线程执行,耗时的任务都被设计成异步回调的形式,以免阻塞主线程。在Promise规范出现之前,经常会出现下面的代码形式。
JavaScript
ajax(url, {...params}, function(result) {
// ... 业务逻辑-1
ajax(url2, {...params}, function(result2) {
// ... 业务逻辑-2
ajax(url2, {...params}, function(result3) {
// ... 业务逻辑-3
});
});
});
形成可怕的回调地狱
的问题,层层的callback回调嵌套,增加了业务逻辑的复杂度,提升了维护成本。 Promise的规范的出现,以及浏览器的实现,以一种顺序的执行逻辑,来改变回调嵌套的这种代码组织方式。
js
// getData函数一般都是现成的request库提供,比如 axios 或 fetch API
function getData(url, param) {
return new Promise((resolve, reject) => {
try {
ajax(url, param, function(result){
resolve(result);
});
} catch (error) {
reject(error);
}
})
}
getData(url).then(result => {
// ... 业务逻辑-1
return getData(url2);
}).then(result2 => {
// ... 业务逻辑-2
return getData(url3);
}).then(result3 => {
// ... 业务逻辑-3
})
通过对比 回调的写法 和 Promise的写法,可以直观感受到Promise API在代码组织形式上的改变。
后来 ES 出现的 async 和 await,更是将异步代码的形式,进一步简化。
js
async function main(params) {
const result = await getData(url, param);
// ... 业务逻辑-1
const result2 = await getData(url2, result);
// ... 业务逻辑-2
const result3 = await getData(url3, result2);
// ... 业务逻辑-3
}
async
和 await
本质是 Promise的语法糖,每一个返回了Promise类型的函数,都可以使用 async 和 await。
Promise规范解读
Promise API都有哪些要点值得我们注意呢?以免在使用的时候掉坑。
- 【要点一】有三种状态:
pending
,fulfilled
,rejected
,默认为 pending 状态 - 【要点二】构造Promise对象的时候,要传递一个构造器函数executor,它会立即执行
- 【要点三】有一个保存成功状态值的
value
属性,取值可是 undefined / thenable / promise - 【要点四】有一个保存失败状态值的
reason
属性 - 【要点五】Promise实例的状态,只能从 pending 到 fulfilled 或 从 pending 到 rejected,一旦确认就不能再改变
- 【要点六】必须有一个then方法,接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected
- 【要点七】如果调用then时,是成功状态,执行
onFulfilled
回调,参数是promise的 value 属性 - 【要点八】如果调用then时,是失败状态,执行
onRejected
回调,参数是promise的 reason 属性 - 【要点九】如果 then 函数中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected内
- 【要点十】实例上的
catch
方法不管被连接到哪里,都能捕获到前面的错误 - 【要点十一】then 或 catch 的回调函数中,返回当前的实例对象,会发生循环引用的错误
- 【要点十二】.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透
遵照上面提出的几个要点,一个Promise规范大致是下面这样的。
js
// 定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 存储成功回调函数
onFulfilledCallbacks = [];
// 存储失败回调函数
onRejectedCallbacks = [];
// 更改成功后的状态
resolve = (value) => {...}
// 更改失败后的状态
reject = (reason) => {...}
then(onFulfilled, onRejected) {...}
}
浏览器的事件循环
浏览器的事件循环 Event Loop
是JS中一个很重要的概念,对于分析代码的执行流程至关重要。
JS中的主线程 运行在 Call stack
,不断的执行调用栈的代码逻辑,遇到同步的代码(console.log
)直接执行,遇到异步的代码,则放入到任务队列 Callback Queue
中,等待合适的时机再运行。一般当 主线程的调用栈没有可执行任务时,就会从任务队列取出一个来,继续执行新一轮的循环,遇到异步任务同样再放到队列,这样循环往复,这就是简单的浏览器事件循环(Event Loop)模型。
这里面的异步任务队列,又分为 宏任务(macro-task)和微任务(micro-task)。常见的宏任务:(一开始的)script脚本代码、网络请求(ajax响应)、setTimeout、setInterval、种事件触发,例如鼠标事件等。常见的微任务:Promise、mutationObserver等。
关于二者的区别。
- 微任务通常比宏任务具有更高的优先级。
- 微任务总是在当前任务执行完成后立即执行。
- 微任务通常是由JavaScript引擎本身创建和调度的
- 宏任务通常具有较低的优先级。
- 宏任务会被推迟到下一轮事件循环再执行,直到JavaScript引擎处于空闲状态时才会执行,
(⭐️)微任务队列中的回调会在本次事件循环结束前清空,而宏任务队列中的回调会在下次事件循环时逐个取出被执行
几道测试题
上的文字描述比较抽象,我们通过几道代码题目加深理解。
题目一
对应【要点十】 catch不管被连接到哪里,都能捕获前面的错误。
promise在构造的时候,reject('error');
触发了一个错误,后面的then-1
、then-2
会被忽略。
js
const promise = new Promise((resolve, reject) => {
reject('error');
resolve('success2');
});
promise
.then((res) => {
console.log('then-1: ', res);
}).then((res) => {
console.log('then-2: ', res);
}).catch((err) => {
// catch不管被连接到哪里,都能捕获上层的错误。
console.log('catch: ', err);
}).then((res) => {
console.log('then-3: ', res);
});
// output:
// catch: error
// then-3: undefined
题目二
resolve(1)之后走的是第一个then方法,并没有走catch里,所以第二个then中的res得到的实际上是第一个then的返回值,return 2会被包装成resolve(2)
js
Promise.resolve(1)
.then((res) => {
console.log(res);
return 2; // return 2会被包装成resolve(2)
})
.catch((err) => 3)
.then((res) => {
console.log(res);
});
// output:
// 1 2
题目三
Promise.reject(1)
透传到catch((err)=> {...})里,再执行最后一个then
js
Promise.reject(1)
.then((res) => {
console.log(res);
return 2;
})
.catch((err) => {
console.log(err);
return 3;
})
.then((res) => { // 3 被传递到 res 变量中
console.log(res);
});
// output:
// 1 3
题目四
返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!') ,也被包裹成了return Promise.resolve(new Error('error!!!'))
js
Promise.resolve().then(() => new Error('error!!!'))
.then((res) => {
console.log('then: ', res);
}).catch((err) => {
console.log('catch: ', err);
});
// output:
// "then: " "Error: error!!!"
题目五
对应【要点十一】,.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
js
const promise = Promise.resolve().then(() => promise);
promise.catch(console.err);
// output:
// Promise {<rejected>: TypeError: Chaining cycle detected for promise #<Promise>}
题目六
对应【要点十二】,.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。
第一个then和第二个then中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了穿透,将resolve(1) 的值直接传到最后一个then里。
js
Promise.resolve(1)
.then(2) //.then的参数里 不是函数被透传
.then(Promise.resolve(3)) //.then的参数里 不是函数被透传
.then(console.log); // 输出最开始的 resolve(1)
// output:
// 1
题目七
finally 最后被执行
js
function promise1 () {
let p = new Promise((resolve) => {
console.log('promise1');
resolve('1')
})
return p;
}
function promise2 () {
return new Promise((resolve, reject) => {
reject('error')
})
}
promise1()
.then(res => {console.log(res); return 222;})
.then(res => console.log(res))
.catch(err => console.log(err))
.finally(() => console.log('finally1'))
promise2()
.then(res => console.log(res))
.catch(err => console.log(err))
.finally(() => console.log('finally2'))
// output:
// 'promise1'
// '1'
// 222
// 'error'
// 'finally2'
// 'finally1'
题目八
Promise.all
,的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。 所有的promise数组都执行完了后,才返回结果。在下面的代码示例中,由于执行runReject(2)
回调时发生了异常,提前中断了整个流程,被catch捕获到。
js
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
function runReject (x) {
const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
.then(res => console.log('then-result: ', res))
.catch(err => console.log('catch-result: ', err))
// 1
// 3
// 2 // 2s后输出
// catch-result: Error: 2
// 4 // 4s后输出
题目九
使用Promise.race()
方法,它只会获取最先执行完成的那个结果,其它的异步任务虽然也会继续进行下去,不过race已经不管那些任务的结果了。 在下面的代码示例中,最后只接收了 runAsync(1)
这个任务的结果
js
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log('then-result: ', res))
.catch(err => console.log('catch-result: ', err))
// output:
// 1
// 'then-result: ' 1
// 2
// 3
题目十
async/await,可以理解成包装的Promise。首先一进来是创建了两个函数的,我们先不看函数的创建位置,而是看它的调用位置 发现async1函数被调用了,然后去看看调用的内容,执行函数中的同步代码async1 start,之后碰到了await,它会阻塞async1后面代码的执行,因此会先去执行async2中的同步代码async2,然后跳出async1 跳出async1函数后,执行同步代码start,在一轮宏任务全部执行完之后,再来执行刚刚await后面的内容async1 end。
js
async function async1() {
console.log("async1 start"); // step-1
await async2(); // 相当于是 Promise.then(() => console.log("async1 end"))
console.log("async1 end"); // step-4
}
async function async2() {
console.log("async2"); // step-2
}
async1();
console.log('start') // step-3
// 'async1 start'
// 'async2'
// 'start'
// 'async1 end'
题目十一
将async结合定时器看看,给上题的 async2函数中加上一个定时器
js
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end"); //微任务-1
}
async function async2() {
setTimeout(() => { // 定时器始终最后执行的,被放到下一轮的宏任务的延迟队列中。
console.log('timer')
}, 0)
console.log("async2");
}
async1();
console.log("start")
// output:
// 'async1 start'
// 'async2'
// 'start'
// 'async1 end'
// 'timer'
结合下面的图,会更容易理解。
题目十二
宏任务和微任务嵌套的情况
js
async function async1() {
console.log("async1 start"); // 主任务-1
await async2();
console.log("async1 end"); //微任务队列-1
setTimeout(() => { // 宏任务队列-3
console.log('timer1')
}, 0)
}
async function async2() {
setTimeout(() => { // 宏任务队列-1
console.log('timer2')
}, 0)
console.log("async2"); // 主任务-2
}
async1();
setTimeout(() => { // // 宏任务队列-2
console.log('timer3')
}, 0)
console.log("start") // 主任务-3
// output:
// 'async1 start'
// 'async2'
// 'start'
// 'async1 end'
// 'timer2'
// 'timer3'
// 'timer1'
结合下面的图,来理解
题目十三
正常情况下,async中的await命令是一个Promise对象,返回该对象的结果。 但如果不是Promise对象的话,就会直接返回对应的值,相当于Promise.resolve()
js
async function fn () {
// return Promise.resolve(1234)
// return await 1234
// 等同于
return 123
}
fn().then(res => console.log(res))
// 结果:123
在async1中await后面的Promise是没有返回值的,也就是它的状态始终是pending状态,因此相当于一直在await,await,await却始终没有响应... 所以在await之后的内容是不会执行的,也包括async1后面的 .then。
题目十四
js
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1'); // 没有resolve,阻塞后面的执行
})
console.log('async1 success');
return 'async1 end'
}
console.log('script start')
async1().then(res => console.log(res))
console.log('script end')
// 'script start'
// 'async1 start'
// 'promise1'
// 'script end'
题目十五
对比十四题,现在Promise有了返回值了,因此await后面的内容将会被执行:
js
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
resolve('promise1 resolve')
}).then(res => console.log(res))
console.log('async1 success');
return 'async1 end'
}
console.log('script start')
async1().then(res => console.log(res))
console.log('script end')
// 'script start'
// 'async1 start'
// 'promise1'
// 'script end'
// 'promise1 resolve'
// 'async1 success'
// 'async1 end'
题目十六
在async1中的new Promise它的 resolve 的值和async1().then()里的值是没有关系的,很多小伙伴可能看到resovle('promise resolve')就会误以为是async1().then()中的返回值。
js
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
resolve('promise resolve')
})
console.log('async1 success');
return 'async1 end'
}
console.log('script start')
async1().then(res => {
console.log('async1-result: ', res)
})
new Promise(resolve => {
console.log('promise2')
setTimeout(() => {
console.log('timer')
})
})
// 'script start'
// 'async1 start'
// 'promise1'
// 'promise2'
// 'async1 success'
// 'async1-result: async1 end'
// 'timer'
题目十七
async、await 和 setTimeout组合
js
async function async1() {
console.log("async1 start"); // step-2
await async2();
console.log("async1 end"); // step-6(微任务-1)
}
async function async2() {
console.log("async2"); // step-3
}
console.log("script start"); // step-1
setTimeout(function() {
console.log("setTimeout"); // step-8(宏任务-3)
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1"); // step-4
resolve();
}).then(function() {
console.log("promise2"); // step-7(微任务-2)
});
console.log('script end') // step-5
// 'script start'
// 'async1 start'
// 'async2'
// 'promise1'
// 'script end'
// 'async1 end'
// 'promise2'
// 'setTimeout'
题目十八
在async中,如果 await后面的内容是一个异常或者错误的话,会怎样呢?await后面跟着的是一个状态为rejected的promise,如果在async函数中抛出了错误,则终止错误结果,不会继续向下执行。
js
async function async1 () {
await async2();
console.log('async1');
return 'async1 success'
}
async function async2 () {
return new Promise((resolve, reject) => {
console.log('async2')
reject('error'); // 如果改为throw new Error也是一样的:
})
}
async1().then(res => console.log(res))
// 'async2'
// Uncaught (in promise) Error: error!!!
题目十九
【要点五】Promise实例的状态,只能从 pending 到 fulfilled 或 从 pending 到 rejected,一旦确认就不能再改变
js
const first = () => (new Promise((resolve, reject) => {
console.log(3); // step-1
let p = new Promise((resolve, reject) => {
console.log(7); // step-2
setTimeout(() => {
console.log(5); // step-6
resolve(6); // 这里无效的resolve,已经 resolve(1)状态不会再改变
console.log(p); // step-7
}, 0)
resolve(1);
});
resolve(2);
p.then((arg) => {
// arg = 1
console.log(arg); // step-4
});
}));
first().then((arg) => {
console.log(arg); // step-5
});
console.log(4); // step-3
// 3
// 7
// 4
// 1
// 2
// 5
// Promise{<resolved>: 1}
题目二十
await new Promise(...)
里面的状态没有改变,会阻塞后面的代码执行
js
const async1 = async () => {
console.log('async1');
setTimeout(() => {
console.log('timer1')
}, 2000)
await new Promise(resolve => {
console.log('promise1') // 因为没有resolved,所以会一直阻塞在这儿,下面的语句【async1().then】不会执行
})
console.log('async1 end')
return 'async1 success'
}
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
.then(2) // then里面不是函数,穿透
.then(Promise.resolve(3)) // then里面不是函数,穿透
.catch(4)
.then(res => console.log(res)) // 因为上面的两次透传,res接收到的是最初的resolve(1)
setTimeout(() => {
console.log('timer2')
}, 1000)
// 'script start'
// 'async1'
// 'promise1'
// 'script end'
// 1
// 'timer2'
// 'timer1'
题目二十一
Promise的状态一旦改变就无法改变,finally不管Promise的状态是resolved还是rejected都会执行,且它的回调函数是没有参数的
js
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve('resolve3');
console.log('timer1')
}, 0)
resolve('resolve1');
resolve('resolve2');
}).then(res => {
console.log(res)
setTimeout(() => {
console.log(p1)
}, 1000)
}).finally(res => {
console.log('finally', res)
})
// 'resolve1'
// 'finally' undefined
// 'timer1'
// Promise{<resolved>: undefined}
题目二十二
使用Promise实现每隔1秒输出1,2,3
js
const ary = [1, 2, 3]
ary.reduce((prev, curr) => {
return prev.then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(curr);
resolve();
}, 1000)
})
})
}, Promise.resolve());
题目二十三
题目描述:JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个
- addTask(1000,"1");
- addTask(500,"2");
- addTask(300,"3");
- addTask(400,"4");
- 最后的输出顺序是:2 3 1 4
js
class Scheduler {
constructor(limit) {
this.maxCounts = limit;
this.queue = [];
this.runningCounts = 0;
this.begin = 0;
}
addTask(time, order) {
const promiseGenerator = function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve(order);
}, time);
});
};
// 把任务包装成 promise,放到队列中
this.queue.push(promiseGenerator);
}
startTask() {
this.begin = Date.now(); // 任务计时开始
// 在最大限制数 【this.maxCounts】,尽可能去运行任务
for (let i = 0; i < this.maxCounts; i++) {
this.doTask();
}
}
doTask() {
if (this.queue.length === 0 || this.runningCounts >= this.maxCounts) return;
// 每当开始运行一个任务,标识位【this.runningCounts】 加一
this.runningCounts++;
this.queue.shift()()
.then((order) => {
console.log(`task-[${order}] done, cost time: ${Date.now() - this.begin}`);
// 每当结束一个任务,标识位【this.runningCounts】 减一
this.runningCounts--;
this.doTask();
});
}
}
const scheduler = new Scheduler(2);
scheduler.addTask(1000, '1');
scheduler.addTask(500, '2');
scheduler.addTask(300, '3');
scheduler.addTask(400, '4');
scheduler.startTask();
// output:
// task-[2] done, cost time: 508
// task-[3] done, cost time: 826
// task-[1] done, cost time: 1010
// task-[4] done, cost time: 1227
一开始1、2两个任务开始执行
- 500ms时,2任务执行完毕,输出2,任务3开始执行
- 800ms时,3任务执行完毕,输出3,任务4开始执行
- 1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
- 1200ms时,4任务执行完毕,输出4
相信吃透上面的 二十多 道题目,你对浏览器的事件循环、JavaScript中的异步任务以及Promise的规范会有更深的理解。
手写实现Promise
这里不再就 Promise 源码的实现一步一步展开了,网上很多的参考文章,这里直接贴出一个完整的实现。
js
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 类
class MyPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 存储成功回调函数
onFulfilledCallbacks = [];
// 存储失败回调函数
onRejectedCallbacks = [];
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// resolve里面将所有成功的回调拿出来执行
while (this.onFulfilledCallbacks.length) {
// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallbacks.shift()(value)
}
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// resolve里面将所有失败的回调拿出来执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
then(onFulfilled, onRejected) {
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
// 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
const newPromise = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
// 创建一个微任务等待 newPromise 完成初始化
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
const rejectedMicrotask = () => {
// 创建一个微任务等待 newPromise 完成初始化
queueMicrotask(() => {
try {
// 调用失败回调,并且把原因返回
const x = realOnRejected(this.reason);
// 传入 resolvePromise 集中处理
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
// 判断状态
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
// 等待
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
})
return newPromise;
}
// resolve 静态方法
static resolve (parameter) {
// 如果传入 MyPromise 就直接返回
if (parameter instanceof MyPromise) {
return parameter;
}
// 转成常规方式
return new MyPromise(resolve => {
resolve(parameter);
});
}
// reject 静态方法
static reject (reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 如果相等了,说明return的是自己,抛出类型错误并返回
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 判断x是不是 MyPromise 实例对象
if(x instanceof MyPromise) {
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
// x.then(value => resolve(value), reason => reject(reason))
// 简化之后
x.then(resolve, reject)
} else{
// 普通值
resolve(x)
}
}
以上的关于【手写实现Promise】代码实现,参考自掘金一位大佬的文章,下面已经给出了引用地址。这里需要强调的是,promise.then
方法返回的是一个promise实例,保证在本轮事件循环内执行,使用了queueMicrotask
这个API。
Window 或 Worker 接口的 queueMicrotask() 方法,将微任务加入队列以在控制返回浏览器的事件循环之前的安全时间执行。微任务是一个简短的函数,它将在当前任务完成其工作后运行,并且在执行上下文的控制权返回到浏览器的事件循环之前没有其他代码等待运行时运行。