今天遇到一个数组排序的坑,是关于ES6的异步语法糖async/await。简单地记录下:
遇坑
起因是需要在forEach的匿名函数里await一个异步函数,于是直观地在匿名函数前加async,简单例子如下,
// 跳序code
function makePromise(num) {
return new Promise(resolve => {
resolve(num)
});
}
[1,2,3,4,5,6].forEach(async (num) => {
if (num % 2 == 0) {
let pmnum = await makePromise(num);
console.log(pmnum)
} else {
console.log(num)
}
});
大家认为打印顺序是什么?这里比较容易误导开发,因为使用await就是为了让代码看起来像同步执行,所以输出应该会是顺序1到6。
然而答案是——1,3,5,2,4,6
结果await后面的代码块被阻塞了。
提问
如果我们提升async的作用域,使其包含遍历,
// 顺序code
async function exc() {
for (let num of [1,2,3,4,5,6]) {
if (num % 2 == 0) {
let pmnum = await makePromise(num);
console.log(pmnum)
} else {
console.log(num)
}
}
}
exc();
1,2,3,4,5,6
这次await并没有阻塞,代码像是同步执行,两者有什么区别呢?是什么导致输出的顺序不一样?
解答
先实现自己的forEach函数,
Array.prototype.myForEach = function(callback) {
for (var i = 0; i < this.length; i++)
callback(this[i], i, this);
};
代替原生的forEach,结果也是跳序1,3,5,2,4,6
。
我们可以看出跳序和顺序code区别在于遍历里的callback是否异步,即前者是在遍历中执行异步方法,后者是在异步方法里执行遍历。跳序code等价于下面的代码段:
// 跳序code2
async function exc2(num) {
if (num % 2 == 0) {
let pmnum = await makePromise(num);
console.log(pmnum)
} else {
console.log(num)
}
}
for (let num of [1,2,3,4,5,6]) {
exc2(num);
}
JavaScript引擎都是单线程执行的,实际上执行到async方法时,无论其方法内部有没有阻塞,引擎都会继续往下跑,所以跳过了246。
了解ES6 await的机制之后,我们知道当运行到await时,会阻塞其后面的代码,但仅限于其所在的async方法内部,简单来说,此时整个async方法都在等待await的返回,运行环境盏又切换到async方法外部,继续执行。
综上所述,跳序code2的运行情况如下
run exc2(1) ↓
run exc2(2) → 遇到await,等待 ↓
run exc2(3) ↓
run exc2(4) → 遇到await,等待 ↓
run exc2(5) ↓
run exc2(6) → 遇到await,等待 ↓
over
异步返回2 → run exc2(2)余下的代码 → over
异步返回4 → run exc2(4)余下的代码 → over
异步返回6 → run exc2(6)余下的代码 → over
所以,输出是1,3,5,2,4,6
课后作业
如果exc2不是async方法,输出的应该是1,2,3,4,5,6
大家可以像上面那样推算一下顺序code的运算顺序。