解决await在forEach中不起作用的问题
一、前言
前两天在项目中用for遍历的时候遇到了一个坑,花了一天的时间解决。这里就记一下。
二、问题
首先引一个很简单题目:给一个数组,每隔1s打印出来.这里我把我一开始在项目中的代码贴出来.(当然这里完全和业务无关的)
const_=require('lodash');
constecho=async(i)=>{
setTimeout(()=>{
console.log('i===>',i);
},5000);
}
letarrs=[1,2,3,4,5,6,7,8,9,10];
consttask=async()=>{
_.forEach(arrs,async(i)=>{
awaitecho(i);
})
}
construn=async()=>{
console.log('run-start====>date:',newDate().toLocaleDateString())
awaittask();
console.log('run-end====>date:',newDate().toLocaleDateString())
}
(async()=>{
console.log('start...')
awaitrun();
console.log('end...')
})()
//start...
//run-start====>date:2018-8-25
//run-end====>date:2018-8-25
//end...
//i===>1
//i===>2
//i===>3
//i===>4
//i===>5
//i===>6
//i===>7
//i===>8
//i===>9
上面的代码和输出已经给出了,很奇怪,这里的await并没有其效果.一开始因为是加了业务,是我的业务代码出了问题,然后我就把代码抽出来了,还是不起作用,当时我是真的对对await怀疑了。
最后还是给出问题的答案:
lodash的forEach和[].forEach不支持await,如果非要一边遍历一边执行await,可使用for-of
这里给出正确的代码:
const_=require('lodash');
constecho=async(i)=>{
returnnewPromise((resolve,reject)=>{
setTimeout(()=>{
console.log('i===>',i,newDate().toLocaleTimeString());
resolve(i);
},2000);
})
}
letarrs=[1,2,3,4,5,6,7,8,9,10];
consttask=async()=>{
//_.forEach(arrs,async(i)=>{
//awaitecho(ji);
//})
//arrs.forEach(async(i)=>{
//awaitecho(i);
//});
for(constiofarrs){
awaitecho(i);
}
}
construn=async()=>{
console.log('run-start====>date:',newDate().toLocaleDateString())
awaittask();
console.log('run-end====>date:',newDate().toLocaleDateString())
}
(async()=>{
console.log('start...')
awaitrun();
console.log('end...')
})()
//输出
start...
run-start====>date:2018-8-26
i===>120:51:29
i===>220:51:31
i===>320:51:33
i===>420:51:35
i===>520:51:37
i===>620:51:39
i===>720:51:42
i===>820:51:44
i===>920:51:46
i===>1020:51:48
run-end====>date:2018-8-26
end...
三、总结
当解决问题的时候,有时候可以使用排除法,比方说在这个例子中,我们知道await这个机制肯定是没问题的,如果真的有问题肯定不会轮到我测出来,那么其实剩下来的问题只能是for遍历的原因了.
因为我一开始是用lodash实现的,那么就可以想是不是lodash的forEach没有作(或者做了多余)await处理,此时就可以换种方式试试了,总的来说还是经验的问题吧。
补充:在forEach中使用async/await遇到的问题
一、问题描述
前几天,项目中遇到一个JavaScript异步问题:
有一组数据,需要对每一个数据进行一个异步处理,并且希望处理的时候是同步的。
用代码描述如下:
//生成数据
constgetNumbers=()=>{
returnPromise.resolve([1,2,3])
}
//异步处理
constdoMulti=num=>{
returnnewPromise((resolve,reject)=>{
setTimeout(()=>{
if(num){
resolve(num*num)
}else{
reject(newError('numnotspecified'))
}
},2000)
})
}
//主函数
constmain=async()=>{
console.log('start');
constnums=[1,2,3];
nums.forEach(async(x)=>{
constres=awaitdoMulti(x);
console.log(res);
});
console.log('end');
};
//执行
main();
在这个例子中,通过forEach遍历地将每一个数字都执行doMulti操作。代码执行的结果是:首先会立即打印start、end。2秒后,一次性输出1,4,9。
这个结果和我们的预期有些区别,我们是希望每间隔2秒,执行一次异步处理,依次输出1,4,9。所以当前代码应该是并行执行了,而我们期望的应该是串行执行。
我们尝试把forEach循环替换成for循环:
constmain=async()=>{
console.log('start');
constnums=awaitgetNumbers();
for(constxofnums){
constres=awaitdoMulti(x);
console.log(res);
}
console.log('end');
};
执行结果完全符合了预期:依次输出:start、1,4,9,end。
二、问题分析
思路都是一样的,只是使用的遍历方式不一样而已,为什么会出现这样的情况呢?在MDN上查找了一下forEach的polyfill参考MDN-Array.prototype.forEach():
//ProductionstepsofECMA-262,Edition5,15.4.4.18
//Reference:http://es5.github.io/#x15.4.4.18
if(!Array.prototype.forEach){
Array.prototype.forEach=function(callback,thisArg){
varT,k;
if(this==null){
thrownewTypeError('thisisnullornotdefined');
}
//1.LetObetheresultofcallingtoObject()passingthe
//|this|valueastheargument.
varO=Object(this);
//2.LetlenValuebetheresultofcallingtheGet()internal
//methodofOwiththeargument"length".
//3.LetlenbetoUint32(lenValue).
varlen=O.length>>>0;
//4.IfisCallable(callback)isfalse,throwaTypeErrorexception.
//See:http://es5.github.com/#x9.11
if(typeofcallback!=="function"){
thrownewTypeError(callback+'isnotafunction');
}
//5.IfthisArgwassupplied,letTbethisArg;elselet
//Tbeundefined.
if(arguments.length>1){
T=thisArg;
}
//6.Letkbe0
k=0;
//7.Repeat,whilek
从上面的polyfill中的setp7,我们可以简单地理解成下面的步骤:
Array.prototype.forEach=function(callback){
//thisrepresentsourarray
for(letindex=0;index
相当于for循环执行了这个异步函数,所以是并行执行,导致了一次性全部输出结果:1,4,9。
constmain=async()=>{
console.log('start');
constnums=awaitgetNumbers();
//nums.forEach(async(x)=>{
//constres=awaitdoMulti(x);
//console.log(res);
//});
for(letindex=0;index{
constres=awaitdoMulti(x)
console.log(res)
})(nums[index])
}
console.log('end');
};
三、解决方案
现在,我们把问题分析清楚了。前面用for-of循环来代替forEach作为解决方案,其实我们也可以改造一下forEach:
constasyncForEach=async(array,callback)=>{
for(letindex=0;index{
console.log('start');
constnums=awaitgetNumbers();
awaitasyncForEach(nums,asyncx=>{
constres=awaitdoMulti(x)
console.log(res)
})
console.log('end');
};
main();
四、Eslint问题
这时候Eslint又报了错:no-await-in-loop。关于这一点,Eslint官方文档https://eslint.org/docs/rules/no-await-in-loop也做了说明。
好的写法:
asyncfunctionfoo(things){
constresults=[];
for(constthingofthings){
//Good:allasynchronousoperationsareimmediatelystarted.
results.push(bar(thing));
}
//Nowthatalltheasynchronousoperationsarerunning,herewewaituntiltheyallcomplete.
returnbaz(awaitPromise.all(results));
}
不好的写法:
asyncfunctionfoo(things){
constresults=[];
for(constthingofthings){
//Bad:eachloopiterationisdelayeduntiltheentireasynchronousoperationcompletes
results.push(awaitbar(thing));
}
returnbaz(results);
}
其实上面两种写法没有什么好坏之分,这两种写法的结果是完全不一样的。Eslint推荐的“好的写法”在执行异步操作的时候没有顺序的,“不好的写法”中有顺序,具体需要用哪种写法还是要根据业务需求来决定。
所以,在文档的WhenNotToUseIt中,Eslint也提到,如果需要有顺序地执行,我们是可以禁止掉该规则的:
Inmanycasestheiterationsofalooparenotactuallyindependentofeach-other.Forexample,theoutputofoneiterationmightbeusedastheinputtoanother.Or,loopsmaybeusedtoretryasynchronousoperationsthatwereunsuccessful.Or,loopsmaybeusedtopreventyourcodefromsendinganexcessiveamountofrequestsinparallel.InsuchcasesitmakessensetouseawaitwithinaloopanditisrecommendedtodisabletheruleviaastandardESLintdisablecomment.
以上为个人经验,希望能给大家一个参考,也希望大家多多支持毛票票。如有错误或未考虑完全的地方,望不吝赐教。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。