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
有A,B,C,D4个case,我要实现A -->> B -->> (C | D),A最先执行,B等待A执行完再执行,最后是(C | D)并发执行,使用ava提供的API来完成case就是:
constava=require('ava')ava.serial('A',async()=>{// do something})ava.serial('B',async()=>{// do something})ava('C',async()=>{// do something})ava('D',async()=>{// do something})
接下来我们就来具体看下AVA内部是如何实现流程控制的:
在AVA内实现了一个Sequence类:
classSequence{constructor(runnables){this.runnables=runnables}run(){// do something}}
具体到我们提供的实例当中:A -->> B -->> (C | D),AVA是如何从这2个类来实现他们之间的按序执行的呢?
在你定义case的时候:
ava.serial('A',async()=>{// do something})ava.serial('B',async()=>{// do something})ava('C',async()=>{// do something})ava('D',async()=>{// do something})
classConcurrent{constructor(runnables,bail){if(!Array.isArray(runnables)){thrownewTypeError('Expected an array of runnables');}this.runnables=runnables;}run(){// 所有的case是否通过letallPassed=true;letpending;letrejectPending;letresolvePending;// 维护一个promise数组constallPromises=[];consthandlePromise=promise=>{// 初始化一个pending的promiseif(!pending){pending=newPromise((resolve,reject)=>{rejectPending=reject;resolvePending=resolve;});}// 如果每个case都返回的是一个promise,那么首先调用then方法添加对于这个promise被resolve或者reject的处理函数,(这个添加被reject的处理,主要是用于下面Promise.all方法来处理所有被resolve的case)同时将这个promise推入到allPromises数组当中allPromises.push(promise.then(passed=>{if(!passed){allPassed=false;if(this.bail){// Stop if the test failed and bail mode is on.resolvePending();}}},rejectPending));};// 通过for循环遍历runnables中保存的case。for(construnnableofthis.runnables){// 调用每个case的run方法constpassedOrPromise=runnable.run();// 如果是同步的case,且执行失败了if(!passedOrPromise){if(this.bail){// Stop if the test failed and bail mode is on.returnfalse;}allPassed=false;}elseif(passedOrPromise!==true){// !!!如果返回的是一个promisehandlePromise(passedOrPromise);}}if(pending){// 使用Promise.all去处理allPromises当中的promise。当所有的promise被resolve后才会调用resolvePending,因为resolvePending对应于pending这个promise的resolve方法,也就是pending这个promise也被resolve,最后调用pending的then方法中添加的对于promise被resolve的方法。Promise.all(allPromises).then(resolvePending);// 返回一个处于pending态的promise,但是它的then方法中添加了这个promise被resolve后的处理函数,即返回allPassedreturnpending.then(()=>allPassed);}// 如果是同步的测试returnallPassed;}}}
最近将内部测试框架的底层库从
mocha
迁移到了AVA
,迁移的原因之一是因为AVA
提供了更好的流程控制。我们从一个例子开始入手:
有
A
,B
,C
,D
4个case,我要实现A -->> B -->> (C | D)
,A
最先执行,B
等待A
执行完再执行,最后是(C | D)
并发执行,使用ava
提供的API来完成case
就是:接下来我们就来具体看下
AVA
内部是如何实现流程控制的:在
AVA
内实现了一个Sequence
类:这个
Sequence
类可以理解成集合的概念,这个集合内部包含的每一个元素可以是由一个case组成,也可以是由多个case组成。这个类的实例当中runnables
属性(数组)保存了需要串行执行的case或case组。一个case可以当做一个组(runnables
),多个case也可以当做一组,AVA
用Sequence
这个类来保证在runnables
中保存的不同元素的顺序执行。顺序执行了解后,我们再看下
AVA
内部实现的另外一个控制case
并行执行的类:Concurrent
:可以将
Concurrent
可以理解为组的概念,实例当中的runnables
属性(数组)保存了这个组中所有待执行的case
。这个Concurrent
和上面提到的Sequence
组都部署了run
方法,用以runnables
的执行,不同的地方在于,这个组内的case都是并行执行的。具体到我们提供的实例当中:
A -->> B -->> (C | D)
,AVA
是如何从这2个类来实现他们之间的按序执行的呢?在你定义case的时候:
在ava内部便会维护一个
serial
数组用以保存顺序执行的case,concurrent
数组用以保存并行执行的case:然后用这2个数组,分别实例化一个
Sequence
和Concurrent
实例:这样保证了
serialTests
内部的case
是顺序执行的,concurrentTests
内部的case
是并行执行的。但是如何保证这2个实例(serialTests
和concurrentTests
)之间的顺序执行呢?即serialTests
内部case
顺序执行完后,再进行concurrentTests
的并行执行。同样是使用
Sequence
这个类,实例化一个Sequence
实例:之前我们就提到过
Sequence
实例的runnables
属性中就维护了串行执行的case
,所以在这里的具体体现就是,serialTests
和concurrentTests
之间是串行执行的,这也对应着:A -->> B -->> (C | D)
。接下来,我们就具体看下对应具体的流程实现:
allTests
是所有这些case
的集合,Sequence
类上部署了run
方法,因此调用:开始
case
的执行。在Sequence
类的run
方法当中:具体到我们提供的例子当中:
allTests
这个Sequence
实例的runnables
属性保存了一个Sequence
实例(A
和B
)和一个Concurrent
实例(C
和D
)。在调用
allTests.run()
后,在对allTesets
的runnables的迭代器对象进行遍历的时候,首先调用包含A
和B
的Sequence
实例的run
方法,在run
内部递归调用runNext
方法,用以确保异步case的顺序执行。具体的实现主要还是使用了
Promise
迭代链来完成异步任务的顺序执行:每次进行异步case时,这个异步的case
会返回一个promise
,这个时候停止迭代器对象的遍历,而是通过在promise
的then
方法中递归调用runNext()
,来保证顺序执行。当A和B组成的
Sequence
执行完成后,才会继续执行由C和D组成的Conccurent
,接下来我们看下并发执行case的内部实现:同样在Concurrent
类上也部署了run
方法,用以开始需要并发执行的case:具体到我们的例子当中:
Concurrent
实例的runnables
属性中保存了C
和D
2个case
,调用实例的run
方法后,C
和D
2个case
即开始并发执行,不同于Sequence
内部通过iterator
遍历器来实现的case
的顺序执行,Concurrent
内部直接只用for
循环来启动case的执行,然后通过维护一个promise
数组,并调用Promise.all
来处理promise
数组的状态。以上就是通过一个简单的例子介绍了
AVA
内部的流程控制模型。简单的总结下:在
AVA
内部使用Promise
来进行整个的流程控制(这里指的异步的case)。串行:
Sequence
类来保证case
的串行执行,在需要串行运行的case
当中,调用Sequence
实例的runNext
方法开始case的执行,通过获取case
数组的iterator对象
来手动对case(或case的集合)
进行遍历执行,因为每个异步的case
内部都返回了一个promise
,这个时候会跳出对iterator
的遍历,通过在这个promise
的then
方法中递归调用runNext
方法,这样就保证了case
的串行执行。并行:
Concurrent
类来保证case
的并行执行,遇到需要并行运行的case
时,同样是使用for
循环,但是不是通过获取数组iterator迭代器
对象去手动遍历,而是并发去执行,同时通过一个数组去收集这些并发执行的case返回的promise
,最后通过Promise.all
方法去处理这些未被resolve
的promise
,当然这里面也有一些小技巧,我在上面的分析中也指出了,这里不再赘述。关于文中提到的Promise进行异步流程控制具体的应用,可以看下这2篇文章:
Promise 异步流程控制
《Node.js设计模式》基于ES2015+的回调控制流
The text was updated successfully, but these errors were encountered: