4. 异步编程题
系列一 async / await
async / await 要点:
- async function 如果没有遇到 await 则一直是按序执行的。
- async function 的 await,相当于一个
promise.then()
运算,则 await 以及下面的代码都在一个微任务中执行。- 即使 await 右侧是一个立即值,也会经过
Promise.resolve()
包裹一下,也是异步微任务。 - ⚠️⚠️⚠️注意:await 右侧的表达式是同步进入并开始执行的,async function 讨论的异步问题是 await 接受 右侧表达式的返回值,如果返回的值需要
promise.then()
处理,这里存在异步问题。
- 即使 await 右侧是一个立即值,也会经过
promise 要点:
- new Promise 中的回调函数是立即执行的,没有异步操作。
- 出现
.then()
、.catch()
操作,就会产生一个微任务。 Promise.resolve()
和Promise.reject()
会产生一个微任务。
题目1
async function bar() {
console.log("22")
return new Promise((resolve) => {
resolve()
})
}
async function foo() {
console.log("11")
await bar()
console.log("33")
}
foo()
console.log("44")
执行顺序:
// 宏任务1
11
22
44
// 微任务1.1
33
题目2
async function async1() {
console.log("async1 start");
await async2(); // 微任务1.1
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () { // 宏任务2
console.log("setTimeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () { // 微任务1.2
console.log("promise2");
});
console.log("script end");
执行顺序:
// 宏任务1
script start
async1 start
async2
promise1
script end
// 微任务1.1
async1 end
// 微任务1.2
promise2
// 宏任务2
setTimeout
题目3
setTimeout(function () {
console.log("setTimeout1"); // 宏2
new Promise(function (resolve) {
resolve();
}).then(function () { // 微2.1
new Promise(function (resolve) {
resolve();
}).then(function () { // 微2.2
console.log("then4");
});
console.log("then2");
});
});
new Promise(function (resolve) {
console.log("promise1"); // 1
resolve();
}).then(function () { // 微1.1
console.log("then1"); // 3
});
setTimeout(function () { // 宏3
console.log("setTimeout2"); // 9
});
console.log(2); // 2
queueMicrotask(() => { // 微1.2
console.log("queueMicrotask1") // 4
});
new Promise(function (resolve) {
resolve();
}).then(function () { // 微1.3
console.log("then3"); // 5
});
执行顺序:
// 宏1
promise1
2
// 微1.1
then1
// 微1.2
queueMicrotask1
// 微1.3
then3
// 宏2
setTimeout1
// 微2.1
then2
// 微2.2
then4
// 宏3
setTimeout2
系列二 new Promise return
题目1
Promise.resolve(1).then((res) => { // then 下一个微任务
console.log(res);
})
new Promise((resolve, reject) => {
console.log(2);
resolve();
}).then(() => console.log(3)); // then 下一个微任务
console.log(4);
Promise.resolve()
不算一次微任务
执行顺序
2
4
1
3
题目2
第一个 Promise,直接 return 4
return 4
相当于:return Promise.resolve(4);
没有额外的微任务。
Promise.resolve().then(() => { // then 下一个微任务
console.log(0);
return 4;
}).then((res) => { // then 下一个微任务
console.log(res);
});
Promise.resolve().then(() => { // then 下一个微任务
console.log(1);
}).then(() => { // then 下一个微任务
console.log(2);
}).then(() => { // then 下一个微任务
console.log(3);
}).then(() => { // then 下一个微任务
console.log(5);
}).then(() => { // then 下一个微任务
console.log(6);
});
执行顺序:
0
1
4
2
3
5
6
题目3
第一个 Promise 中,return thenable。这里分为两个步骤:
- Promise 会先执行 then,尝试得到一个立即值。
- 然后
Promise.resolve()
包裹返回。
遇到非立即值。Promise 会把接下来的代码放到下一个微任务中。
⚠️:Promise 调用 then 方法,会放在下一个微任务中。也就是说,如果 return thenable
方法,会跳到下一个微任务中执行。
Promise.resolve().then(() => { // then 下一个微任务
console.log(0);
return {
then: function(resolve) { // thenable 下一个微任务
console.log('then function');
resolve(4);
}
}
}).then((res) => { // then 下一个微任务
console.log(res);
})
Promise.resolve().then(() => { // then 下一个微任务
console.log(1);
}).then(() => { // then 下一个微任务
console.log(2);
}).then(() => { // then 下一个微任务
console.log(3);
}).then(() => { // then 下一个微任务
console.log(5);
}).then(() => { // then 下一个微任务
console.log(6);
});
结果:
0
1
the function
2
4
3
5
6
题目4
第一个 Promise 中,return Promise.resolve()
。
遇到非立即值。Promise 会把接下来的代码放到下一个微任务中。
- 额外的「记」:
return Promise.resolve()
会放到下下一个微任务中,即推迟 2 次。- 对比上一题 4 的打印时机,可以看到当前题 4 位置靠后了 1 位。
Promise.resolve().then(() => { // then 下一个微任务
console.log(0);
return Promise.resolve(4); // 📒❗️下下一个微任务(推迟2次)
}).then((res) => { // then 下一个微任务
console.log(res);
})
Promise.resolve().then(() => { // then 下一个微任务
console.log(1);
}).then(() => { // then 下一个微任务
console.log(2);
}).then(() => { // then 下一个微任务
console.log(3);
}).then(() => { // then 下一个微任务
console.log(5);
}).then(() => { // then 下一个微任务
console.log(6);
});
测试结果:
0 1 2 3 4 5 6
系列三 node 环境
node 的事件循环,知识点见:Node 的事件循环
通常要考虑以下几个任务队列:
main script | nexttick 微 | other 微 | timer 宏| check 宏
- poll 队列是 IO 操作,通常不会考察。
注意:
- 微任务优先执行的。微任务中:
process.nextTick
在 nexttick 队列中,是优先执行的;- Promise的 then 回调、
queueMicrotask
是靠后执行的。
- 宏任务队列中:
setTimeout
、setInterval
在 timer 队列中,是优先执行的;setImmediate
是最后执行的。
🀄️总结:和浏览器不同,node 中需要额外注意两点,在一个 tick 中:
process.nextTick
是微任务,是最先执行的。setImmediate
是宏任务,是最后执行的。
题目1
async function async1() {
console.log('async1 start') // 2
await async2() // 微2
console.log('async1 end') // 9
}
async function async2() {
console.log('async2') // 3
}
console.log('script start') // 1
setTimeout(function () { // 宏1
console.log('setTimeout0') // 11
}, 0)
setTimeout(function () { // 宏300,推迟执行
console.log('setTimeout2') // 13
}, 300)
setImmediate(() => console.log('setImmediate')); // 宏2 // 12
process.nextTick(() => console.log('nextTick1')); //微1 // 7
async1();
process.nextTick(() => console.log('nextTick2')); // 微1 // 8
new Promise(function (resolve) {
console.log('promise1') // 4
resolve();
console.log('promise2') // 5
}).then(function () { // 微2
console.log('promise3') // 10
})
console.log('script end') // 6
注意
- 最后一个 Promise 中,promise1 和 promise2 是同步执行的。
- 第三行
await async2()
中,对async2()
的调用时同步执行的。 - 第二个
setTimeout
推迟 300 毫秒执行,不在下一个 tick 中,只有推迟 0 毫秒的在下一个 tick,所以该方法的执行比代码中的setImmediate
靠后。
script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end
promise3
setTimeout0
setImmediate
setTimeout2