forEach和for of的执行异步顺序问题

4,734 阅读2分钟

我们写代码的时候,通常希望代码能按照我们写的顺序来执行;例如:

async function test() {
	let arr = [3, 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()

我们更希望上边的实行结果为

3
2
1
结束

但是,真实情况是

结束
1
2
3

why

想知道问题的原因,就的知道forEach底层代码到底是怎么样的

const myForEach = function(fn) {
    let i
    for(i=0; i<this.length; i++){
       fn(this[i], i)
    }      
}

可以看到,forEach 拿过来直接执行了,这就导致它无法保证异步任务的执行顺序。

solution

解决方法: for of

async function test() {
    let arr = [3, 2, 1]
    for(const item of arr) {
        const res = await handle(item)
        console.log(res)
    }
	console.log('结束')
}

function handle(x) {
	return new Promise((resolve, reject) => {
	    setTimeout(() => {
    	    resolve(x)
           }, 1000 * x)
	})
}

test()

结果:

3
2
1
结束

what is the fuck about ( for of)?

其实for...of并不像forEach那么简单粗暴的方式去遍历执行,而是采用一种特别的手段——迭代器 去遍历。 首先,对于数组来讲,它是一种可迭代数据类型。那什么是可迭代数据类型呢?

可迭代数据类型:原生具有[Symbol.iterator]属性数据类型为可迭代数据类型。如数组、类数组(如arguments、NodeList)、Set和Map。

let arr = [3, 2, 1];
// 这就是迭代器
let iterator = arr[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

// {value: 3, done: false}
// {value: 2, done: false}
// {value: 1, done: false}
// {value: undefined, done: true}

好,现在我们把iterator用一到我们最开始的代码中;如下

function handle(x) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve(x)
		}, 1000 * x)
	})
}

async function test() {
  let arr = [3, 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 = iterator.next();
  }
	console.log('结束')
}
test()

打印一下结果:

// 3
// 2
// 1
// 结束

结论: 多个任务成功地按照顺序执行;那么回到for...of ,for...of其实是这段代码的语法糖。