JS异步控制流及async实现细节分析(3)

8. series/parallel/parallelLimit

async.eachOfSeries(arr, iterator, callback)是对arr中的每一项,调用iterator函数,最终调用callback。也就是说,所有的异步任务都是同一种类型,只是传入的参数不同。例如对于一个目录下的所有文件,统计每个文件的size。

因此async.eachOfSeries无法处理多个不同类型的异步任务的情况,例如多个异步任务,一个是请求远程数据,一个是判断文件是否存在,还有一些其它类型的任务。这种情况下需要一个新的函数series

series是基于async.eachOfSeries来实现,基本逻辑如下:


  • 每个task的回调,记作taskCallback
  • async.eachOfSeries第二个参数中的回调,记作iteratorCallback
  • async.eachOfSeries的第三个参数也是一个回调(相关代码参看eachOfSeries的实现),记作eachOfSeriesCallback
  • series的第二个参数,记作seriesCallback

现在来分析series的执行流程:

  • 依次执行每个task,在这个过程中,会由用户调用taskCallback([1])
  • taskCallback其实是在series的实现中定义([2])
  • 在执行taskCallback的时候,会先将task的结果储存在结果集合中([3]),然后调用iteratorCallback([4])
  • iteratorCallback的作用是判断任务是否执行完毕,如果没有执行完毕,则继续执行下一个任务;如果任务执行完毕,则执行eachOfSeriesCallback(该部分的逻辑在async.eachOfSeries中实现)
  • 当所有task执行完毕后,或者任务执行过程中出错时,会执行eachOfSeriesCallback([5])
  • eachOfSeriesCallback中,执行seriesCallback([6]),至此,series的执行过程结束

parallel/parallelLimitseries的实现类似,只不过是将async.eachOfSeries换成了async.eachOf/async.eachOfLimit来实现。

9. waterfall

series模型的多个任务之间是没有依赖关系的。但是如果一个任务的执行依赖于上一个任务的结果,series就无法处理了,此时需要使用waterfall,即每个任务的执行结果会传递给下一个任务。

一个简单的实现思路是:将每一步任务的结果储存在一个变量中,然后将该变量传入下一个任务作为参数。基本逻辑如下:


当然,这只是一个非常简陋的实现,仅仅用来描述waterfall的实现逻辑。在async的源码中,async.waterfall的实现如下:

首先来看async.iterator,它的作用是将一个任务列表转换成一个迭代器结构,例如:


该函数源码不难看懂,在此不做赘述。

接下来看wrapIterator,其中有一个_restParam比较影响阅读,我们把该函数改写为如下形式:

总的来说,wrapIterator返回了一个可以递归调用的函数,从而可以依次执行任务。这段代码比较不容易理解,但是实现的非常巧妙。

接下来看ensureAsync的实现(已经改写为去掉_restParam的形式从而方便阅读):

这与async.eachOfSeries中的实现是基本一致的。那么为什么要确保任务的执行是异步呢?这是因为:在一个任务中调用callback会去执行下一个任务,假如所有的任务都是同步调用,而且任务非常多,那么就会导致栈溢出。

关于该情况下栈溢出问题,可以通过如下例子来模拟:

如果n非常大,例如simulateOverflow(100000),就会导致如下错误:

如果将fns[k - 1]()改成setImmediate(fns[k - 1]),就不会有问题了。

1 2 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部