问题:对于异步代码,forEach 并不能保证按顺序执行。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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()
|
我们期望的结果是:
但是实际上会输出:
问题原因
这是为什么呢?我想我们有必要看看forEach
底层怎么实现的。
1 2 3 4 5 6 7
| for (var i = 0; i < length; i++) { if (i in array) { var element = array[i]; callback(element, i, array); } }
|
可以看到,forEach 拿过来直接执行了,这就导致它无法保证异步任务的执行顺序。比如后面的任务用时短,那么就又可能抢在前面的任务之前执行。
解决方案
如何来解决这个问题呢?
其实也很简单, 我们利用for...of
就能轻松解决。
1 2 3 4 5 6 7 8
| 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。
可迭代对象可以通过迭代器进行遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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());
|
因此,我们的代码可以这样来组织:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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('结束') }
|
多个任务成功地按顺序执行!其实刚刚的for…of循环代码就是这段代码的语法糖。
重新认识生成器
回头再看看用iterator遍历[4,2,1]这个数组的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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
和done
属性,生成器也可以调用 next,返回的也是这样的数据结构,这么巧?!
没错,生成器本身就是一个迭代器。
既然属于迭代器,那它就可以用for…of遍历了吧?
当然没错,不信来写一个简单的斐波那契数列(50以内):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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); }
|
是不是非常酷炫?这就是迭代器的魅力:)同时又对生成器
有了更深入的理解,没想到我们的老熟人Generator
还有这样的身份。
以上便是本文的全部内容,希望对你有所启发。