当前位置: 首页 > 科技观察

你可以使用数组遍历,但是Promise版本呢?

时间:2023-03-14 21:07:18 科技观察

这里指的遍历方法有:map,reduce,reduceRight,forEach,filter,some,every,因为最近要做一些数据汇总,node版本已经是8.11.1了,所以直接写了异步/等待脚本。但是在对数组进行一些遍历操作的时候,我们发现有些遍历方法反馈给Promise的并不是我们想要的结果。当然,有些并不是严格意义上的遍历,比如some和every。但实际上,这些将根据我们数组的元素多次调用传入回调。这些方法是比较常见的,但是当你的回调函数是Promise时,一切都变了。前言async/await是Promise语法糖文将直接使用async/await代替Promiseletresult=awaitfunc()//=>等同于func().then(result=>{//codehere})//======asyncfunctionfunc(){return1}//=>相当于functionfunc(){returnnewPromise(resolve=>resolve(1))}mapmap可以说是对Promise最友好的函数了。我们都知道map接收两个参数:一个是对每个元素执行的回调,回调结果的返回值将作为对应下标元素的可选回调函数this指向的参数[1,2,3]在数组中。map(item=>item**2)//对数组元素求平方//>[1,4,9]上面是一个普通的map执行,但是当我们的一些计算操作变成异步的时候:[1,2,3.map(asyncitem=>item**2)//对数组元素进行平方//>[Promise,Promise,Promise]这时候我们得到的返回值其实是数组的一个Promise函数。那么为什么上面说map函数是最友好的呢,因为我们知道Promise有一个函数叫做Promise.all,它会依次执行一个由Promises组成的数组,返回一个Promise对象,其结果就是这个数组生成的结果集.awaitPromise.all([1,2,3].map(asyncitem=>item**2))//>[1,4,9]先用Promise.all包裹数组,再用await得到结果。reduce/reduceRightreduce的函数签名想必大家都不陌生。它接收两个参数:1.为每个元素执行的回调函数,返回值会在下一次函数调用时累加。回调函数的签名:accumulator的累加值currentValue当前正在处理的元素currentIndex当前正在处理的元素下标数组调用reduce的数组2.可选的初始化值将作为累加器的初始值[1,2,3].reduce((accumulator,item)=>accumulator+item,0)//加法//>6这段代码也可以,如果我们的加法操作也是异步的:[1,2,3].reduce(async(accumulator,item)=>accumulator+item,0)//添加/>Promise{:"[objectPromise]3"}返回的结果会很奇怪,我们回头看reduce函数above签名为每个元素执行一个回调函数,返回值将被添加到下一个函数调用中。然后我们再看代码,async(accumulator,item)=>accumulator+=item,开头也提到了,是Pormise语法糖,为了看得更清楚,我们可以这样写:(accumulator,item)=>newPromise(resolve=>resolve(accumulator+=item))也就是说我们reduce回调函数的返回值其实是一个Promise对象然后我们对Promise对象进行+=操作,就有意义了得到这么奇怪的返回值。当然reduce的调整也很容易:await[1,2,3].reduce(async(accumulator,item)=>awaitaccumulator+item,0)//>6我们在accumulator上调用await,然后和当前item求和,我们的reduce返回值也一定是***处的Promise,所以我们也在最外面加上await这个词,意思就是我们每次reduce都会返回一个新的Promise对象,我们会得到对象里面Promise的结果。我们所说的reduce实际上得到了这样一个Promise对象:newPromise(resolve=>{letitem=3newPromise(resolve=>{letitem=2newPromise(resolve=>{letitem=1Promise.resolve(0)).then(result=>resolve(item+result))}).then(result=>resolve(item+result))}).then(result=>resolve(item+result))})reduceRight无话可说。.正好和reduce的执行顺序相反。forEachforEach,这应该是用的最多的遍历方法,对应的函数签名:1.callback,调用每个元素的函数currentValue,当前元素索引,当前元素下标数组,调用forEach的数组引用2.thisArg,一个可选回调函数this指向以下操作://获取数组元素[1,2,3]的平方值.forEach(item=>{console.log(item**2)})//>1//>4//>9普通版,我们可以直接这样输出,但是如果遇到Promise//获取数组元素[1,2,3]的平方值.forEach(asyncitem=>{console.log(item**2)})//>nothingforEach不关心回调函数的返回值,所以forEach只是执行了三个返回Promise的函数所以我们要想得到想要的效果只能自己增强:Array.prototype.forEachSync=asyncfunction(callback,thisArg){for(let[index,item]ofObject.entries(this)){awaitcallback(item,index,this))}}await[1,2,3].forEachSync(asyncitem=>{console.log(item**2)})//>1/>4//>9await会忽略非Promise值,await0,awaitundefined和普通代码没什么区别。filterfilter是一个过滤数组的函数,也有遍历的作用:函数签名和forEach一样,只是回调返回值为true的元素会放在filter函数的返回值中。我们要进行奇数过滤,所以我们这样写:[1,2,3].filter(item=>item%2!==0)//>[1,3]然后我们换成Promise版本:[1,2,3].filter(asyncitem=>item%2!==0)//>[1,2,3]这会导致我们的过滤函数失败,因为filter的返回值匹配不是一个精确的match,只要能将返回值转换为true,就认为通过了过滤。Promise对象必须为真,因此过滤器无效。所以我们的处理方式和上面的forEach类似,同样需要自己对对象进行增强,不过我们这里直接选择了取巧的方式:Array.prototype.filterSync=asyncfunction(callback,thisArg){letfilterResult=awaitPromise.all(this.map(callback))//>[true,false,true]returnthis.filter((_,index)=>filterResult[index])}await[1,2,3].filterSync(item=>item%2!==0)我们可以在内部直接调用map方法,因为我们知道map会将所有的返回值作为一个新的数组返回。这也意味着我们的地图可以得到我们过滤所有项目的结果,不管是真还是假。接下来返回原数组每一项对应下标的结果。somesome作为一个函数存在,用于检测一个数组是否满足某些条件。也可以用来遍历。该函数的签名与forEach相同。不同的是,当任何一个回调返回值匹配true时,都会直接返回true。如果所有的回调匹配都是false,那么返回false我们需要判断数组中是否有元素等于2:[1,2,3].some(item=>item===2)/>true那么我们把它改成Promise[1,2,3].some(asyncitem=>item===2)//>true这个函数还是会返回true,但这不是我们想要的,因为async返回的Promise对象被认为是真实的。因此,我们必须执行以下操作:Array.prototype.someSync=asyncfunction(callback,thisArg){for(let[index,item]ofObject.entries(this)){if(awaitcallback(item,index,this))returntrue}returnfalse}await[1,2,3].someSync(asyncitem=>item===2)//>true因为some会在匹配到第一个true后终止遍历,所以我们这里使用forEach是一种浪费性能。也是利用了await忽略普通表达式的特点,内部使用for-of来实现我们需要的every和我们最新的every函数签名也和forEach一样,但是在回调的处理上还是有些区别的:在其实,从另一个角度来说,every是一个反转,some会在第一个true的时候结束,every会在第一个false的时候结束。如果所有元素都为真,则返回真。判断数组中是否所有元素都大于3[1,2,3].every(item=>item>3)//>false显然没有匹配到,回调函数一直执行到第***次已经终止,不再继续执行。我们改成Promise版本:[1,2,3].every(async=>it??em>3)//>true这个一定是真的,因为我们判断的是Promise对象,所以我们稍微修改一下上面的someSync实现:Array.prototype.everySync=asyncfunction(callback,thisArg){for(let[index,item]ofObject.entries(this)){if(!awaitcallback(item,index,this))returnfalse}returntrue}await[1,2],3].everySync(asyncitem=>item===2)/>false当匹配到任何false时,直接返回false,结束遍历。关于数组的这些遍历方法的后记。因为map和reduce的特性,是使用async时变化最小的函数。reduce的结果很像一个洋葱模型,但是对于其他的遍历函数,目前需要自己实现。四个*Sync函数的实现:https://github.com/Jiasm/notebook/tree/master/array-sync