问题:对于异步代码,forEach 并不能保证按顺序执行。
举个例子:
async function test() {let arr = [4, 2, 1]arr.forEach(async item => {const res = await handle(item)console.log(res)})console.log('结束')}function handle(x) {return new Promise((resolve, reject) => {setTimeout(() => {resolve(x)}, 1000 * x)})}test()
我们期望的结果是:
421结束
但是实际上会输出:
结束124
问题原因
这是为什么呢?我想我们有必要看看forEach底层怎么实现的。
// 核心逻辑for (var i = 0; i < length; i++) {if (i in array) {var element = array[i];callback(element, i, array);}}
可以看到,forEach 拿过来直接执行了,这就导致它无法保证异步任务的执行顺序。比如后面的任务用时短,那么就又可能抢在前面的任务之前执行。
解决方案
如何来解决这个问题呢?
其实也很简单, 我们利用for...of就能轻松解决。
async function test() {let arr = [4, 2, 1]for(const item of arr) {const res = await handle(item)console.log(res)}console.log('结束')}
解决原理——Iterator
好了,这个问题看起来好像很简单就能搞定,你有想过这么做为什么可以成功吗?
其实,for…of并不像forEach那么简单粗暴的方式去遍历执行,而是采用一种特别的手段——迭代器去遍历。
首先,对于数组来讲,它是一种可迭代数据类型。那什么是可迭代数据类型呢?
原生具有[Symbol.iterator]属性数据类型为可迭代数据类型。如数组、类数组(如arguments、NodeList)、Set和Map。
可迭代对象可以通过迭代器进行遍历。
let arr = [4, 2, 1];// 这就是迭代器let iterator = arr[Symbol.iterator]();console.log(iterator.next());console.log(iterator.next());console.log(iterator.next());console.log(iterator.next());// {value: 4, done: false}// {value: 2, done: false}// {value: 1, done: false}// {value: undefined, done: true}
因此,我们的代码可以这样来组织:
async function test() {let arr = [4, 2, 1]let iterator = arr[Symbol.iterator]();let res = iterator.next();while(!res.done) {let value = res.value;console.log(value);await handle(value);res = iterater.next();}console.log('结束')}// 4// 2// 1// 结束
多个任务成功地按顺序执行!其实刚刚的for…of循环代码就是这段代码的语法糖。
重新认识生成器
回头再看看用iterator遍历[4,2,1]这个数组的代码。
let arr = [4, 2, 1];// 迭代器let iterator = arr[Symbol.iterator]();console.log(iterator.next());console.log(iterator.next());console.log(iterator.next());console.log(iterator.next());// {value: 4, done: false}// {value: 2, done: false}// {value: 1, done: false}// {value: undefined, done: true}
咦?返回值有value和done属性,生成器也可以调用 next,返回的也是这样的数据结构,这么巧?!
没错,生成器本身就是一个迭代器。
既然属于迭代器,那它就可以用for…of遍历了吧?
当然没错,不信来写一个简单的斐波那契数列(50以内):
function* fibonacci(){let [prev, cur] = [0, 1];console.log(cur);while(true) {[prev, cur] = [cur, prev + cur];yield cur;}}for(let item of fibonacci()) {if(item > 50) break;console.log(item);}// 1// 1// 2// 3// 5// 8// 13// 21// 34
是不是非常酷炫?这就是迭代器的魅力:)同时又对生成器有了更深入的理解,没想到我们的老熟人Generator还有这样的身份。
以上便是本文的全部内容,希望对你有所启发。
