译者:前端小智javascript
阿里云最近在作活动,低至2折,有兴趣能够看看:promotion.aliyun.com/ntms/yunpar…前端
为了保证的可读性,本文采用意译而非直译。 async
与 await
的使用方式相对简单。 蛤当你尝试在循环中使用await
时,事情就会变得复杂一些。java
在本文中,分享一些在若是循环中使用await
值得注意的问题。git
对于这篇文章,假设你想从水果篮中获取水果的数量。github
const fruitBasket = {
apple: 27,
grape: 0,
pear: 14
};
复制代码
你想从fruitBasket
得到每一个水果的数量。 要获取水果的数量,可使用getNumFruit
函数。数组
const getNumFruit = fruit => {
return fruitBasket[fruit];
};
const numApples = getNumFruit('apple');
console.log(numApples); //27
复制代码
如今,假设fruitBasket
是从服务器上获取,这里咱们使用 setTimeout
来模拟。promise
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
};
const getNumFruit = fruit => {
return sleep(1000).then(v => fruitBasket[fruit]);
};
getNumFruit("apple").then(num => console.log(num)); // 27
复制代码
最后,假设你想使用await
和getNumFruit
来获取异步函数中每一个水果的数量。服务器
const control = async _ => {
console.log('Start')
const numApples = await getNumFruit('apple');
console.log(numApples);
const numGrapes = await getNumFruit('grape');
console.log(numGrapes);
const numPears = await getNumFruit('pear');
console.log(numPears);
console.log('End')
}
复制代码
首先定义一个存放水果的数组:app
const fruitsToGet = [“apple”, “grape”, “pear”];
复制代码
循环遍历这个数组:
const forLoop = async _ => {
console.log('Start');
for (let index = 0; index < fruitsToGet.length; index++) {
// 获得每一个水果的数量
}
console.log('End')
}
复制代码
在for
循环中,过上使用getNumFruit
来获取每一个水果的数量,并将数量打印到控制台。
因为getNumFruit
返回一个promise
,咱们使用 await
来等待结果的返回并打印它。
const forLoop = async _ => {
console.log('start');
for (let index = 0; index < fruitsToGet.length; index ++) {
const fruit = fruitsToGet[index];
const numFruit = await getNumFruit(fruit);
console.log(numFruit);
}
console.log('End')
}
复制代码
当使用await
时,但愿JavaScript暂停执行,直到等待 promise 返回处理结果。这意味着for
循环中的await
应该按顺序执行。
结果正如你所预料的那样。
“Start”;
“Apple: 27”;
“Grape: 0”;
“Pear: 14”;
“End”;
复制代码
这种行为适用于大多数循环(好比while
和for-of
循环)…
可是它不能处理须要回调的循环,如forEach
、map
、filter
和reduce
。在接下来的几节中,咱们将研究await
如何影响forEach
、map和filter
。
首先,使用 forEach
对数组进行遍历。
const forEach = _ => {
console.log('start');
fruitsToGet.forEach(fruit => {
//...
})
console.log('End')
}
复制代码
接下来,咱们将尝试使用getNumFruit
获取水果数量。 (注意回调函数中的async
关键字。咱们须要这个async
关键字,由于await
在回调函数中)。
const forEachLoop = _ => {
console.log('Start');
fruitsToGet.forEach(async fruit => {
const numFruit = await getNumFruit(fruit);
console.log(numFruit)
});
console.log('End')
}
复制代码
我指望控制台打印如下内容:
“Start”;
“27”;
“0”;
“14”;
“End”;
复制代码
但实际结果是不一样的。在forEach
循环中等待返回结果以前,JavaScrip先执行了 console.log('End')。
实际控制台打印以下:
‘Start’
‘End’
‘27’
‘0’
‘14’
复制代码
JavaScript 中的 forEach
不支持 promise 感知,也不支持 async
和await
,因此不能在 forEach
使用 await
。
若是在map
中使用await
, map
始终返回promise
数组,这是由于异步函数老是返回promise
。
const mapLoop = async _ => {
console.log('Start')
const numFruits = await fruitsToGet.map(async fruit => {
const numFruit = await getNumFruit(fruit);
return numFruit;
})
console.log(numFruits);
console.log('End')
}
“Start”;
“[Promise, Promise, Promise]”;
“End”;
复制代码
若是你在 map
中使用 await
,map
老是返回promises
,你必须等待promises
数组获得处理。 或者经过await Promise.all(arrayOfPromises)
来完成此操做。
const mapLoop = async _ => {
console.log('Start');
const promises = fruitsToGet.map(async fruit => {
const numFruit = await getNumFruit(fruit);
return numFruit;
});
const numFruits = await Promise.all(promises);
console.log(numFruits);
console.log('End')
}
复制代码
运行结果以下:
若是你愿意,能够在promise
中处理返回值,解析后的将是返回的值。
const mapLoop = _ => {
// ...
const promises = fruitsToGet.map(async fruit => {
const numFruit = await getNumFruit(fruit);
return numFruit + 100
})
// ...
}
“Start”;
“[127, 100, 114]”;
“End”;
复制代码
当你使用filter
时,但愿筛选具备特定结果的数组。假设过滤数量大于20的数组。
若是你正常使用filter
(没有 await),以下:
const filterLoop = _ => {
console.log('Start')
const moreThan20 = fruitsToGet.filter(async fruit => {
const numFruit = await fruitBasket[fruit]
return numFruit > 20
})
console.log(moreThan20)
console.log('END')
}
复制代码
运行结果
Start
["apple"]
END
复制代码
filter
中的await
不会以相同的方式工做。 事实上,它根本不起做用。
const filterLoop = async _ => {
console.log('Start')
const moreThan20 = await fruitsToGet.filter(async fruit => {
const numFruit = fruitBasket[fruit]
return numFruit > 20
})
console.log(moreThan20)
console.log('END')
}
// 打印结果
Start
["apple", "grape", "pear"]
END
复制代码
为何会发生这种状况?
当在filter
回调中使用await
时,回调老是一个promise
。因为promise
老是真的,数组中的全部项都经过filter
。在filter
使用 await
类如下这段代码
const filtered = array.filter(true);
复制代码
在filter
使用 await
正确的三个步骤
使用map
返回一个promise 数组
使用 await
等待处理结果
使用 filter
对返回的结果进行处理
const filterLoop = async _ => { console.log('Start');
const promises = await fruitsToGet.map(fruit => getNumFruit(fruit));
const numFruits = await Promise.all(promises);
const moreThan20 = fruitsToGet.filter((fruit, index) => { const numFruit = numFruits[index]; return numFruit > 20; })
console.log(moreThan20); console.log('End') }
若是想要计算 fruitBastet
中的水果总数。 一般,你可使用reduce
循环遍历数组并将数字相加。
const reduceLoop = _ => {
console.log('Start');
const sum = fruitsToGet.reduce((sum, fruit) => {
const numFruit = fruitBasket[fruit];
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
复制代码
运行结果:
当你在 reduce
中使用await
时,结果会变得很是混乱。
const reduceLoop = async _ => {
console.log('Start');
const sum = await fruitsToGet.reduce(async (sum, fruit) => {
const numFruit = await fruitBasket[fruit];
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
复制代码
[object Promise]14
是什么 鬼??
剖析这一点颇有趣。
在第一次遍历中,sum
为0
。numFruit
是27
(经过getNumFruit(apple)
的获得的值),0 + 27 = 27
。
在第二次遍历中,sum
是一个promise
。 (为何?由于异步函数老是返回promises
!)numFruit
是0
.promise 没法正常添加到对象,所以JavaScript将其转换为[object Promise]
字符串。 [object Promise] + 0
是object Promise] 0
。
在第三次遍历中,sum
也是一个promise
。 numFruit
是14
. [object Promise] + 14
是[object Promise] 14
。
解开谜团!
这意味着,你能够在reduce
回调中使用await
,可是你必须记住先等待累加器!
const reduceLoop = async _ => {
console.log('Start');
const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
const sum = await promisedSum;
const numFruit = await fruitBasket[fruit];
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
复制代码
可是从上图中看到的那样,await
操做都须要很长时间。 发生这种状况是由于reduceLoop
须要等待每次遍历完成promisedSum
。
有一种方法能够加速reduce
循环,若是你在等待promisedSum以前先等待getNumFruits()
,那么reduceLoop
只须要一秒钟便可完成:
const reduceLoop = async _ => {
console.log('Start');
const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
const numFruit = await fruitBasket[fruit];
const sum = await promisedSum;
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
复制代码
这是由于reduce
能够在等待循环的下一个迭代以前触发全部三个getNumFruit
promise。然而,这个方法有点使人困惑,由于你必须注意等待的顺序。
在reduce中使用wait最简单(也是最有效)的方法是
使用map
返回一个promise 数组
使用 await
等待处理结果
使用 reduce
对返回的结果进行处理
const reduceLoop = async _ => { console.log('Start');
const promises = fruitsToGet.map(getNumFruit); const numFruits = await Promise.all(promises); const sum = numFruits.reduce((sum, fruit) => sum + fruit);
console.log(sum) console.log('End') }
这个版本易于阅读和理解,须要一秒钟来计算水果总数。
若是你想连续执行await
调用,请使用for
循环(或任何没有回调的循环)。
永远不要和forEach
一块儿使用await
,而是使用for
循环(或任何没有回调的循环)。
不要在 filter
和 reduce
中使用 await
,若是须要,先用 map
进一步骤处理,而后在使用 filter
和 reduce
进行处理。
为了回馈读者,《大迁世界》不按期举行(每月一到三次),现金抽奖活动,保底200,外加用户赞扬,但愿你能成为大迁世界的小锦鲤,快来试试吧
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,便可看到福利,你懂的。
每次整理文章,通常都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励