乐趣区

关于asyncawait-与forEach-的组合陷阱

明天遇到一个数组排序的坑,是对于 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 的运算程序。

退出移动版