跳到主要内容

4. 异步编程题

系列一 async / await

async / await 要点:

  • async function 如果没有遇到 await 则一直是按序执行的。
  • async function 的 await,相当于一个 promise.then() 运算,则 await 以及下面的代码都在一个微任务中执行。
    • 即使 await 右侧是一个立即值,也会经过 Promise.resolve() 包裹一下,也是异步微任务。
    • ⚠️⚠️⚠️注意:await 右侧的表达式是同步进入并开始执行的,async function 讨论的异步问题是 await 接受 右侧表达式的返回值,如果返回的值需要 promise.then() 处理,这里存在异步问题。

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。这里分为两个步骤:

  1. Promise 会先执行 then,尝试得到一个立即值。
  2. 然后 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 是靠后执行的。
  • 宏任务队列中:
    • setTimeoutsetInterval 在 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