You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
/** * Runs promises from array of functions that can return promises * in chained manner * * @param {array} arr - promise arr * @return {Object} promise object */functionrunPromiseInSequence(arr,input){returnarr.reduce((promiseChain,currentFunction)=>promiseChain.then(currentFunction),Promise.resolve(input));}// promise function 1functionp1(a){returnnewPromise((resolve,reject)=>{resolve(a*5);});}// function 3 - will be wrapped in a resolved promise by .then()functionf3(a){returna*3;}// promise function 4functionp4(a){returnnewPromise((resolve,reject)=>{resolve(a*4);});}constpromiseArr=[p1,f3,p4];runPromiseInSequence(promiseArr,10).then(console.log);// 600
背景
最近看到一个比较有意思的题目,如何实现异步的
reduce
。数组原生的reduce
方法非常强大,合理使用可以使代码更加简洁明朗,如果实现异步版本 ,能做的事情就多了,这是非常有价值的一件事情。原生
reduce
简介reduce
是 ES5 版本新增的数组方法,它挂载在JavaScript
内置对象Array
的原型上,只能处理同步的场景。reducer
的理念也影响了状态管理库redux
的设计。MDN 上是这样描述的:
示例如下:
可以观察到,它接收两个参数, 回调函数(
callback
)是必传参数,第二项初始值(initialValue
)非必传。reduce
执行时,会内部遍历数组每一项,执行callback
函数,并将它的返回值accumulator
当作callback
的第一位参数传入。MDN 介绍它的参数有个点吸引了我的注意:
第二个参数(
initialValue
)的提供与否,会影响到数组索引0值的处理逻辑:也就是第一次执行
callback
时,它的第一个参数传入的,要么是初始值,要么是数组第一项。怎么实现一个异步版本呢?先找一下有没有类似的实现。
MDN上的异步版本
MDN上有一个比较满足需求的实现,代码如下(为了篇幅删减了部分代码):
可以看到,核心函数是
runPromiseInSequence
方法,它对原生reduce
进行了一次封装,利用Promise chain
控制异步执行顺序,数组的每一项都是函数,在遍历执行时当作回调函数传入.then
方法,可谓是非常精巧。到这里我们的目的已经达到了,文章。。。完。
开个小玩笑,我们来分析一下:
优点很明显,通过简单的封装,确实实现了异步流程控制,但缺点也很明显:
首先,数组的每一项必须是
function
,以前使用reduce
的一些数组比如['a', 'b' ,'c'].reduce(...)
无法平滑的切换到该方法,适用场景受到了限制。假如数组中的项不为function
,必须包装一层,因为.then
方法只接收回调函数,这是第一点;第二点,
runPromiseInSequence
本身的问题,直接上代码:第三点,需要自己手动实现
Promise chain
链;是否可以不对数组每一项的类型做限制,并且将链式调用封装到方法内部呢?
实现一个异步的
reduce
吧!同步版本的实现
先来写一个同步
reduce
,理解下它内部的实现原理,再考虑改造。文章开头介绍过原生
reduce
方法的大致情况,了解了这些信息,可以着手构建一个reduce
函数了。笔者打算写一个独立函数,不将方法放到Array
的原型上实现,迭代数组直接当作一个参数传入,下面是一个具体实现:一个同步的
reduce
实现到这里,我们已经实现了
reduce
的同步版本,如何将它改造成异步函数呢?异步版本的实现
首先要确定异步的方式,回调 or
Promise
。 回调的问题这里不做过多的赘述,现代工程中的异步,构筑在强大的Promise
对象之上,Promise
很适合做异步流程管理。我们可以在遍历数组时做一些文章,允许callback
返回一个Promise
,等待它状态凝固后再进行下一次的遍历。for
循环不支持基于Promise
的异步,需要寻找一个替代品,ES9 加入了异步的“for”循环写法,即for await...of
,用于遍历异步可迭代对象。基于
for await...of
的异步reduce
实现上述的代码调用循环的前一行,生成了新的对象
iterableSource
,主要为了保存待迭代对象的索引,因为使用for await...of
进行迭代时,只能拿到当前迭代的值,无法拿到它的索引。如果
array
很大,这么做无疑会有性能问题,最优解应部署Symbol.asyncIterator
接口,有兴趣的小伙伴可自行实现,这里仅提供思路。for await...of
语法比较新,能否仅仅使用ES6
的语法实现这个特性呢,答案是肯定的,循环可用递归代替。基于递归 +
Promise
的异步reduce
实现写到这里,尽管没有尽善尽美,比如:未对稀疏数组做处理(借用
for...in
遍历键名),但异步流程控制的功能已经迁移到reduce
函数内部实现,功能也做到了兼容数组各类值。希望本文可以为你带来一点启发。参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
The text was updated successfully, but these errors were encountered: