UNPKG

async-kit

Version:

A simple and powerful async abstraction lib for easily writing Node.js code.

1,548 lines (1,334 loc) 114 kB
# BDD Spec The Mocha framework is used for BDD-style tests. To help understand the following tests, here are the three helper functions used: ```js function createStats( n ) { var i ; var stats = { startCounter: [] , endCounter: [] , order: [] , plan: { then: 0 , 'else': 0 , 'catch': 0 , 'finally': 0 } , exec: { then: 0 , 'else': 0 , 'catch': 0 , 'finally': 0 } } ; for ( i = 0 ; i < n ; i ++ ) { stats.startCounter[ i ] = stats.endCounter[ i ] = 0 ; } return stats ; } function asyncJob( stats , id , delay , options , result , callback ) { var realResult = result.slice() ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; if ( typeof options.failCount === 'number' && options.failCount >= stats.endCounter[ id ] && ! ( result[ 0 ] instanceof Error ) ) { realResult[ 0 ] = new Error( "Planned failure" ) ; } callback.apply( undefined , realResult ) ; } , delay ) ; } function syncJob( stats , id , options , result , callback ) { var realResult = result.slice() ; stats.startCounter[ id ] ++ ; stats.endCounter[ id ] ++ ; stats.order.push( id ) ; if ( typeof options.failCount === 'number' && options.failCount >= stats.endCounter[ id ] && ! ( result[ 0 ] instanceof Error ) ) { realResult[ 0 ] = new Error( "Planned failure" ) ; } callback.apply( undefined , realResult ) ; } ``` The Full BDD spec generated by Mocha: # TOC - [async.series()](#asyncseries) - [async.parallel()](#asyncparallel) - [Jobs](#jobs) - [Jobs & async.Plan.prototype.using()](#jobs--asyncplanprototypeusing) - [passing a function to .using()](#jobs--asyncplanprototypeusing-passing-a-function-to-using) - [passing an array to .using()](#jobs--asyncplanprototypeusing-passing-an-array-to-using) - [Jobs scheduling with async.prototype.nice()](#jobs-scheduling-with-asyncprototypenice) - [Jobs & async.Plan.prototype.execMapping(), adding input arguments to .exec()](#jobs--asyncplanprototypeexecmapping-adding-input-arguments-to-exec) - [*this*](#this) - [JobContext.abort()](#jobcontextabort) - [ExecContext.getJobsStatus()](#execcontextgetjobsstatus) - [async.foreach()](#asyncforeach) - [async.map()](#asyncmap) - [async.reduce()](#asyncreduce) - [async.waterfall()](#asyncwaterfall) - [async.race()](#asyncrace) - [async.while()](#asyncwhile) - [async.do().while()](#asyncdowhile) - [async.do().repeat()](#asyncdorepeat) - [Async conditional](#async-conditional) - [async.if.and()](#async-conditional-asyncifand) - [async.if.or()](#async-conditional-asyncifor) - [async.and()](#async-conditional-asyncand) - [async.or()](#async-conditional-asyncor) - [nested async.or() and async.and() in async.if()](#async-conditional-nested-asyncor-and-asyncand-in-asyncif) - [async.Plan.prototype.boolean()](#async-conditional-asyncplanprototypeboolean) - [async.Plan.prototype.then(), .else(), .catch(), .finally(), .execThenCatch(), .execThenElse() and .execThenElseCatch()](#asyncplanprototypethen-else-catch-finally-execthencatch-execthenelse-and-execthenelsecatch) - [async.Plan.prototype.timeout()](#asyncplanprototypetimeout) - [async.Plan.prototype.retry()](#asyncplanprototyperetry) - [Mixing async.Plan.prototype.retry() & async.Plan.prototype.timeout()](#mixing-asyncplanprototyperetry--asyncplanprototypetimeout) - [async.Plan.prototype.parallel()](#asyncplanprototypeparallel) - [async.Plan.prototype.fatal()](#asyncplanprototypefatal) - [async.Plan.prototype.lastJobOnly()](#asyncplanprototypelastjobonly) - [async.Plan.prototype.mapping1to1()](#asyncplanprototypemapping1to1) - [async.Plan.prototype.execKV()](#asyncplanprototypeexeckv) - [Events](#events) - [async.callTimeout()](#asynccalltimeout) - [async.wrapper.timeout()](#asyncwrappertimeout) - [Misc tests](#misc-tests) - [Async EventEmitter](#async-eventemitter) - ['Maximum call stack size exceeded' prevention](#maximum-call-stack-size-exceeded-prevention) <a name=""></a> <a name="asyncseries"></a> # async.series() should run the series of job which do not have errors, in the good order, and trigger the callback with the correct result. ```js var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` when a job has error, it should start running a series of job, be interrupted by that error and return it. ```js var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ new Error() , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ new Error() , 'wonderful' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.order ).to.eql( [ 0, 1 ] ) ; done() ; } ) ; ``` when a function is given instead of an array of job, it should format the result using the returnLastResultOnly mode. ```js var stats = createStats( 1 ) ; async.series( function ( callback ) { asyncJob( stats , 0 , 50 , {} , [ undefined , 'my wonderful result' ] , callback ) ; } ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.equal( 'my wonderful result' ) ; expect( stats.endCounter ).to.eql( [ 1 ] ) ; expect( stats.order ).to.eql( [ 0 ] ) ; done() ; } ) ; ``` when a function is given instead of an array of job that transmit error, it should be directly transmited as the global error. ```js var stats = createStats( 1 ) ; async.do( function ( callback ) { asyncJob( stats , 0 , 50 , {} , [ new Error() , 'my wonderful result' ] , callback ) ; } ) .exec( function( error , results ) { expect( error ).to.be.an( Error ) ; expect( results ).to.equal( 'my wonderful result' ) ; expect( stats.endCounter ).to.eql( [ 1 ] ) ; expect( stats.order ).to.eql( [ 0 ] ) ; done() ; } ) ; ``` <a name="asyncparallel"></a> # async.parallel() should run jobs which do not have errors in parallel, and trigger the callback with the correct result. ```js var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ; done() ; } ) ; ``` when a job has error, it should start running jobs in parallel, be interrupted by that error and trigger callback with it before other pending jobs can complete. ```js var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ new Error() , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).to.be.an( Error ) ; expect( results ).to.eql( [ undefined , undefined , [ new Error() , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 0, 0, 1 ] ) ; expect( stats.order ).to.eql( [ 2 ] ) ; done() ; } ) ; ``` when the slower job has error, it should start running jobs in parallel, all other job complete and it trigger callback with the error. ```js var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ new Error() , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ new Error() , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ; done() ; } ) ; ``` <a name="jobs"></a> # Jobs can be an array of async function accepting a completion callback. ```js var stats = createStats( 3 ) ; async.series( [ function( callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'my' ) ; } , 0 ) ; } , function( callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'wonderful' ) ; } , 0 ) ; } , function( callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'result' ) ; } , 0 ) ; } ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` can be an array of synchronous function, if it still accept and use the completion callback. ```js var stats = createStats( 3 ) ; async.series( [ function( callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'my' ) ; } , function( callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'wonderful' ) ; } , function( callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'result' ) ; } ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` can be an array of array, each of them having a async function as the first element and then a list of argument to pass to this function, it should accept one more argument: the callback for completion being added by the async lib. ```js var stats = createStats( 3 ) ; async.do( [ [ asyncJob , stats , 0 , 0 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` can be an array of array, each of them having a synchronous function as the first element and then a list of argument to pass to this function, if those functions still accept and use the completion callback. ```js var stats = createStats( 3 ) ; async.do( [ [ syncJob , stats , 0 , {} , [ undefined , 'my' ] ] , [ syncJob , stats , 1 , {} , [ undefined , 'wonderful' ] ] , [ syncJob , stats , 2 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` can be an array of async.Plan, each of them will be used by calling their .exec() method. ```js var stats = createStats( 6 ) ; async.parallel( [ async.series( [ [ asyncJob , stats , 0 , 10 , {} , [ undefined , 'a' ] ] , [ asyncJob , stats , 1 , 10 , {} , [ undefined , 'nice' ] ] , [ asyncJob , stats , 2 , 10 , {} , [ undefined , 'output' ] ] ] ) , async.series( [ [ asyncJob , stats , 3 , 10 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 4 , 10 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 5 , 10 , {} , [ undefined , 'result' ] ] ] ) ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results[ 0 ][ 0 ] ).not.to.be.an( Error ) ; expect( results[ 1 ][ 0 ] ).not.to.be.an( Error ) ; expect( results[ 0 ][ 1 ] ).to.eql( [ [ undefined , 'a' ], [ undefined , 'nice' ], [ undefined , 'output' ] ] ) ; expect( results[ 1 ][ 1 ] ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1, 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 3, 1, 4, 2, 5 ] ) ; done() ; } ) ; ``` can be an array that mix all those type of jobs. ```js var stats = createStats( 7 ) ; async.parallel( [ function( callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "I'm an async anonymous function" ) ; } , 0 ) ; } , function( callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "I'm a synchronous anonymous function" ) ; } , async.series( [ [ asyncJob , stats , 2 , 20 , {} , [ undefined , 'nested' ] ] , [ asyncJob , stats , 3 , 20 , {} , [ undefined , 'async.Plan' ] ] , [ asyncJob , stats , 4 , 20 , {} , [ undefined , 'results' ] ] ] ) , [ syncJob , stats , 5 , {} , [ undefined , "I'm a synchronous array of function and arguments" ] ] , [ asyncJob , stats , 6 , 10 , {} , [ undefined , "I'm an async array of function and arguments" ] ] , ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , "I'm an async anonymous function" ] , [ undefined , "I'm a synchronous anonymous function" ] , [ undefined , [ [ undefined , "nested" ] , [ undefined , "async.Plan" ] , [ undefined , "results" ] ] ] , [ undefined , "I'm a synchronous array of function and arguments" ] , [ undefined , "I'm an async array of function and arguments" ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1, 1, 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 1, 5, 0, 6, 2, 3, 4 ] ) ; done() ; } ) ; ``` objects can be used instead of array as the top container, the results should be an objects with the same properties mapping, properties' order should be preserved (*IF* they do not start with a digit - because of V8 behaviours with objects). ```js var stats = createStats( 3 ) ; async.parallel( { one: [ asyncJob , stats , 0 , 40 , {} , [ undefined , 'my' ] ] , two: [ asyncJob , stats , 1 , 20 , {} , [ undefined , 'wonderful' ] ] , three: [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] } ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( { one: [ undefined , 'my' ], two: [ undefined , 'wonderful' ], three: [ undefined , 'result' ] } ) ; expect( Object.keys( results ) ).to.eql( [ 'one' , 'two' , 'three' ] ) ; // Check the keys order expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; done() ; } ) ; ``` <a name="jobs--asyncplanprototypeusing"></a> # Jobs & async.Plan.prototype.using() <a name="jobs--asyncplanprototypeusing-passing-a-function-to-using"></a> ## passing a function to .using() should take each job as an array of arguments to pass to the .using()'s function. ```js var stats = createStats( 3 ) ; async.do( [ [ stats , 0 , 0 , {} , [ undefined , 'my' ] ] , [ stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] , [ stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .using( asyncJob ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` when the job is not an array, it should take each job as the first argument to pass to the .using()'s function. ```js var id = 0 , stats = createStats( 3 ) ; async.do( [ 'my' , 'wonderful' , 'result' ] ) .using( function( data , callback ) { stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; id ++ ; callback( undefined , data ) ; } , 0 ) ; } ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` <a name="jobs--asyncplanprototypeusing-passing-an-array-to-using"></a> ## passing an array to .using() when a job is a function, it should take the .using()'s array as argument. ```js var stats = createStats( 3 ) ; async.parallel( [ function( data , callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "DESCRIPTION: " + data.describe ) ; } , 20 ) ; } , function( data , callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "LENGTH: " + data.body.length ) ; } , 10 ) ; } , function( data , callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "BODY: " + data.body ) ; } , 0 ) ; } ] ) .using( [ { describe: 'some data' , body: 'blahblihblah' } ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'DESCRIPTION: some data' ] , [ undefined , 'LENGTH: 12' ] , [ undefined , 'BODY: blahblihblah' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; done() ; } ) ; ``` <a name="jobs-scheduling-with-asyncprototypenice"></a> # Jobs scheduling with async.prototype.nice() using .nice( -2 ), it should run the series of job with synchonous scheduling. ```js var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( -2 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` using .nice( -1 ), it should run the series of job with an async scheduling (setImmediate). ```js var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( -1 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` using .nice( 10 ), it should run the series of job with an async scheduling (setTimeout 100ms). ```js var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( 10 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` using .nice( -2 ), it should run the jobs in parallel with synchonous scheduling. ```js var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( -2 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ; done() ; } ) ; ``` using .nice( -1 ), it should run the jobs in parallel with an async scheduling (setImmediate). ```js var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( -1 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ; done() ; } ) ; ``` using .nice( 10 ), it should run the jobs in parallel with an async scheduling (setTimeout 100ms). ```js var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( 10 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ; done() ; } ) ; ``` <a name="jobs--asyncplanprototypeexecmapping-adding-input-arguments-to-exec"></a> # Jobs & async.Plan.prototype.execMapping(), adding input arguments to .exec() using default exec()'s arguments mapping, called with no argument, it should not throw error. ```js var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , function( callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "result" ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } , 0 ) ; } ] ) .exec() ; ``` using default exec()'s arguments mapping, when a job is a function, it should take the input arguments passed to .exec(). ```js var stats = createStats( 3 ) ; async.parallel( [ function( describe , body , callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "DESCRIPTION: " + describe ) ; } , 20 ) ; } , function( describe , body , callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "LENGTH: " + body.length ) ; } , 10 ) ; } , function( describe , body , callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "BODY: " + body ) ; } , 0 ) ; } ] ) .exec( 'some data' , 'blahblihblah' , function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'DESCRIPTION: some data' ] , [ undefined , 'LENGTH: 12' ] , [ undefined , 'BODY: blahblihblah' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; done() ; } ) ; ``` when a job is a function, it should take the input arguments passed to .exec(). ```js var stats = createStats( 3 ) ; async.parallel( [ function( describe , body , callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "DESCRIPTION: " + describe ) ; } , 20 ) ; } , function( describe , body , callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "LENGTH: " + body.length ) ; } , 10 ) ; } , function( describe , body , callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "BODY: " + body ) ; } , 0 ) ; } ] ) .execMapping( { callbacks: [ 'finally' ] , minInputs: 2 , maxInputs: 2 , inputsName: [ 'describe' , 'body' ] } ) .exec( 'some data' , 'blahblihblah' , function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'DESCRIPTION: some data' ] , [ undefined , 'LENGTH: 12' ] , [ undefined , 'BODY: blahblihblah' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; done() ; } ) ; ``` when mixing arguments passed to .exec() and .using(), .exec()'s arguments overlapping .using()'s arguments should overwrite. ```js var stats ; var asyncPlan = async.parallel( [ function( describe , body , callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "DESCRIPTION: " + describe ) ; } , 20 ) ; } , function( describe , body , callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "LENGTH: " + body.length ) ; } , 10 ) ; } , function( describe , body , callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "BODY: " + body ) ; } , 0 ) ; } ] ) .using( [ "<insert .using()'s description here>" , "<insert .using()'s body here>" ] ) .execMapping( { callbacks: [ 'finally' ] , minInputs: 0 , maxInputs: 2 , inputsName: [ 'describe' , 'body' ] } ) ; stats = createStats( 3 ) ; asyncPlan.exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , "DESCRIPTION: <insert .using()'s description here>" ] , [ undefined , 'LENGTH: 29' ] , [ undefined , "BODY: <insert .using()'s body here>" ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; stats = createStats( 3 ) ; asyncPlan.exec( "<insert .exec()'s description here>" , function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , "DESCRIPTION: <insert .exec()'s description here>" ] , [ undefined , 'LENGTH: 29' ] , [ undefined , "BODY: <insert .using()'s body here>" ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; stats = createStats( 3 ) ; asyncPlan.exec( "<insert .exec()'s description here>" , "<insert .exec()'s body here>" , function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , "DESCRIPTION: <insert .exec()'s description here>" ] , [ undefined , 'LENGTH: 28' ] , [ undefined , "BODY: <insert .exec()'s body here>" ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; done() ; } ) ; } ) ; } ) ; ``` <a name="this"></a> # *this* each job function should have *this* set to the current jobContext, jobCallback.jobContext should be an alternate way to access it. ```js var stats = createStats( 3 ) ; async.series( [ function( callback ) { var id = 0 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( this.execContext.results ).to.eql( [ undefined ] ) ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'my' ) ; } , 0 ) ; } , function( callback ) { var id = 1 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( this.execContext.results ).to.eql( [ [ undefined , 'my' ] , undefined ] ) ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'wonderful' ) ; } , 0 ) ; } , function( callback ) { var id = 2 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( this.execContext.results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], undefined ] ) ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'result' ) ; } , 0 ) ; } ] ) .exec( done ) ; ``` using()'s function should have *this* set to the current jobContext. ```js var stats = createStats( 3 ) ; async.series( [ [ 0 , 'my' , [ undefined ] ] , [ 1 , 'wonderful' , [ [ undefined , 'my' ] , undefined ] ] , [ 2 , 'result' , [ [ undefined , 'my' ], [ undefined , 'wonderful' ], undefined ] ] ] ) .using( function( id , result , expectedThisResults , callback ) { expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( this.execContext.results ).to.eql( expectedThisResults ) ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , result ) ; } , 0 ) ; } ) .exec( done ) ; ``` every user provided callback should have *this* set to the current execContext. ```js var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 0 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .then( function( results ) { expect( this ).to.be.an( async.ExecContext ) ; expect( this.results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; } ) .finally( function( error , results ) { expect( this ).to.be.an( async.ExecContext ) ; expect( this.results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; } ) .execThenCatch( function( results ) { expect( this ).to.be.an( async.ExecContext ) ; expect( this.results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; } , function( error ) {} , function( error , results ) { expect( this ).to.be.an( async.ExecContext ) ; expect( this.results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; done() ; } ) ; ``` a job can register to the 'timeout' event, that will be triggered when using .timeout() when the job exceed the time limit. ```js var stats = createStats( 3 ) ; var timeoutArray = [ false , false , false ] ; async.parallel( [ function( callback ) { var id = 0 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; stats.startCounter[ id ] ++ ; this.on( 'timeout' , function() { timeoutArray[ id ] = true ; } ) ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'my' ) ; } , 20 ) ; } , function( callback ) { var id = 1 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; stats.startCounter[ id ] ++ ; this.on( 'timeout' , function() { timeoutArray[ id ] = true ; } ) ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'wonderful' ) ; } , 60 ) ; } , function( callback ) { var id = 2 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; stats.startCounter[ id ] ++ ; this.on( 'timeout' , function() { timeoutArray[ id ] = true ; } ) ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'result' ) ; } , 0 ) ; } ] ) .timeout( 40 ) .exec( function( error , results ) { expect( error ).to.be.ok() ; expect( results ).to.eql( [ [ undefined, 'my' ] , [ new async.AsyncError( 'jobTimeout' ) ] , [ undefined, 'result' ] ] ) ; expect( timeoutArray ).to.be.eql( [ false , true , false ] ) ; done() ; } ) ; ``` <a name="jobcontextabort"></a> # JobContext.abort() should start a series of sync job, one of them call this.abort(), so it should abort the whole job's queue. ```js var stats = createStats( 3 ) ; async.series( [ [ syncJob , stats , 0 , {} , [ undefined , 'my' ] ] , [ syncJob , stats , 1 , { abort: true } , [ undefined , 'wonderful' ] ] , [ syncJob , stats , 2 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ] ] ) ; expect( stats.startCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.order ).to.eql( [ 0, 1 ] ) ; done() ; } ) ; ``` should start a series of async job, one of them call this.abort(), so it should abort the whole job's queue. ```js var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 20 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 50 , { abort: true } , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ] ] ) ; expect( stats.startCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.order ).to.eql( [ 0, 1 ] ) ; done() ; } ) ; ``` a job within a while loop, calling this.abort(), cannot abort the whole loop. ```js var stats = createStats( 3 ) ; var count = -1 ; async.while( function( error , results , callback ) { count ++ ; callback( count < 3 ) ; } ) .do( [ function( callback ) { stats.startCounter[ count ] ++ ; stats.endCounter[ count ] ++ ; stats.order.push( count ) ; if ( count === 1 ) { this.abort( undefined , count ) ; } else { callback( undefined , count ) ; } } ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined, 2 ] ] ) ; expect( stats.startCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` should run a job within a while loop, one of them call this.abortLoop(), so it should abort the whole loop. ```js var stats = createStats( 3 ) ; var count = -1 ; async.while( function( error , results , callback ) { count ++ ; callback( count < 3 ) ; } ) .do( [ function( callback ) { stats.startCounter[ count ] ++ ; stats.endCounter[ count ] ++ ; stats.order.push( count ) ; if ( count === 1 ) { this.abortLoop( undefined , count ) ; } else { callback( undefined , count ) ; } } ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined, 1 ] ] ) ; expect( stats.startCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.order ).to.eql( [ 0, 1 ] ) ; done() ; } ) ; ``` <a name="execcontextgetjobsstatus"></a> # ExecContext.getJobsStatus() should return the real-time status of all jobs at any time (here in series mode). ```js async.series( [ function one( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( undefined , 'ok' ) ; } , 20 ) ; } , function two( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( new Error( 'somethingBadHappens' ) ) ; } , 20 ) ; } , function three( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'failed' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( undefined , 'should timeout!' ) ; } , 60 ) ; } , function four( callback ) { var self = this , jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'failed' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'timeout' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { self.abort() ; } , 0 ) ; } , function five( callback ) { expect().fail( "This code should never be reached" ) ; } ] ) .timeout( 40 ) .fatal( false ) .exec( function( error , results ) { var jobsStatus = this.getJobsStatus() ; expect( error ).not.to.be.ok() ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'failed' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'timeout' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'aborted' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; done() ; } ) ; ``` should return the real-time status of all jobs at any time (here in parallel mode). ```js async.parallel( [ function one( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( undefined , 'ok' ) ; } , 20 ) ; } , function two( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( new Error( 'somethingBadHappens' ) ) ; } , 20 ) ; } , function three( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( undefined , 'should timeout!' ) ; } , 60 ) ; } , function four( callback ) { var self = this , jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { self.abort() ; } , 30 ) ; } , function five( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'pending' ) ; } ] ) .timeout( 40 ) .fatal( false ) .exec( function( error , results ) { var jobsStatus = this.getJobsStatus() ; expect( error ).not.to.be.ok() ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'failed' ) ; // Sometime 'timeout' kick in before execThen(), but it should be considered as a normal behaviour //expect( jobsStatus[ 2 ].status ).to.be( 'pending' ) ; expect( [ 'pending' , 'timeout' ] ).to.contain( jobsStatus[ 2 ].status ) ; expect( jobsStatus[ 3 ].status ).to.be( 'aborted' ) ; // Same here //expect( jobsStatus[ 4 ].status ).to.be( 'pending' ) ; expect( [ 'pending' , 'timeout' ] ).to.contain( jobsStatus[ 4 ].status ) ; done() ; } ) ; ``` should report the number of retry of a job, as well as the list of successives errors. ```js var jobOneTries = 1 , jobTwoTries = 1 ; async.parallel( [ function one( callback ) { setTimeout( function() { if ( jobOneTries < 6 ) { callback( new Error( 'somethingBadHappens' + jobOneTries ) ) ; } else { callback( undefined , 'ok' ) ; } jobOneTries ++ ; } , 20 ) ; } , function two( callback ) { setTimeout( function() { if ( jobTwoTries < 2 ) { callback( new Error( 'somethingBadHappens' + jobTwoTries ) ) ; } else { callback( undefined , 'ok' ) ; } jobTwoTries ++ ; } , 20 ) ; } ] ) .retry( 3 , 5 ) .exec( function( error , results ) { var jobsStatus = this.getJobsStatus() ; //console.log( jobsStatus ) ; expect( error ).to.be.ok() ; expect( jobsStatus[ 0 ].status ).to.be( 'failed' ) ; expect( jobsStatus[ 0 ].tried ).to.be( 4 ) ; expect( jobsStatus[ 0 ].errors.length ).to.be( 4 ) ; expect( jobsStatus[ 0 ].errors[ 0 ].message ).to.be( 'somethingBadHappens1' ) ; expect( jobsStatus[ 0 ].errors[ 1 ].message ).to.be( 'somethingBadHappens2' ) ; expect( jobsStatus[ 0 ].errors[ 2 ].message ).to.be( 'somethingBadHappens3' ) ; expect( jobsStatus[ 0 ].errors[ 3 ].message ).to.be( 'somethingBadHappens4' ) ; expect( jobsStatus[ 0 ].result ).to.eql( [ new Error( 'somethingBadHappens4' ) ] ) ; expect( jobsStatus[ 1 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].tried ).to.be( 2 ) ; expect( jobsStatus[ 1 ].errors.length ).to.be( 1 ) ; expect( jobsStatus[ 1 ].errors[ 0 ].message ).to.be( 'somethingBadHappens1' ) ; expect( jobsStatus[ 1 ].result ).to.eql( [ undefined, 'ok' ] ) ; done() ; } ) ; ``` should give insight when a job call its callback twice or more. ```js async.parallel( [ function one( callback ) { setTimeout( function() { callback( undefined , 'ok' ) ; callback( undefined , 'callback used twice' ) ; callback( undefined , 'callback used three times' ) ; } , 20 ) ; } , function two( callback ) { setTimeout( function() { callback( undefined , 'ok' ) ; } , 20 ) ; } ] ) .exec( function( error , results ) { var jobsStatus = this.getJobsStatus() ; //console.log( jobsStatus ) ; expect( error ).not.to.be.ok() ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 0 ].tried ).to.be( 1 ) ; expect( jobsStatus[ 0 ].errors.length ).to.be( 2 ) ; expect( jobsStatus[ 0 ].errors[ 0 ].message ).to.be( 'This job has called its completion callback 2 times' ) ; expect( jobsStatus[ 0 ].errors[ 1 ].message ).to.be( 'This job has called its completion callback 3 times' ) ; expect( jobsStatus[ 0 ].result ).to.eql( [ undefined , 'ok' ] ) ; expect( jobsStatus[ 1 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].tried ).to.be( 1 ) ; expect( jobsStatus[ 1 ].errors.length ).to.be( 0 ) ; expect( jobsStatus[ 1 ].result ).to.eql( [ undefined, 'ok' ] ) ; done() ; } ) ; ``` <a name="asyncforeach"></a> # async.foreach() should take each job as an element to pass to the iterator function. ```js var stats = createStats( 3 ) ; var myArray = [ { id: 0 , timeout: 10 , result: [ undefined , 'my' ] } , { id: 1 , timeout: 0 , result: [ undefined , 'wonderful' ] } , { id: 2 , timeout: 0 , result: [ undefined , 'result' ] } ] ; async.foreach( myArray , function( element , callback ) { stats.startCounter[ element.id ] ++ ; setTimeout( function() { stats.endCounter[ element.id ] ++ ; stats.order.push( element.id ) ; callback.apply( undefined , element.result ) ; } , element.delay ) ; } ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` when the *iterator* accepts three arguments, the current key (array's index) is passed to it as the second argument. ```js var stats = createStats( 3 ) ; var myArray = [ { id: 0 , timeout: 10 , result: [ undefined , 'my' ] } , { id: 1 , timeout: 0 , result: [ undefined , 'wonderful' ] } , { id: 2 , timeout: 0 , result: [ undefined , 'result' ] } ] ; async.foreach( myArray , function( element , key , callback ) { stats.startCounter[ element.id ] ++ ; expect( key ).to.equal( element.id ) ; setTimeout( function() { stats.endCounter[ element.id ] ++ ; stats.order.push( element.id ) ; callback.apply( undefined , element.result ) ; } , element.delay ) ; } ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` if the container to iterate is an object, the current key (property name) is passed to it as the second argument. ```js var stats = createStats( 3 ) ; var myObject = { one: { id: 0 , name: 'one' , timeout: 10 , result: [ undefined , 'my' ] } , two: { id: 1 , name: 'two' , timeout: 0 , result: [ undefined , 'wonderful' ] } , three: { id: 2 , name: 'three' , timeout: 0 , result: [ undefined , 'result' ] } } ; async.foreach( myObject , function( element , key , callback ) { stats.startCounter[ element.id ] ++ ; expect( key ).to.equal( element.name ) ; setTimeout( function() { stats.endCounter[ element.id ] ++ ; stats.order.push( element.id ) ; callback.apply( undefined , element.result ) ; } , element.delay ) ; } ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( { one: [ undefined , 'my' ], two: [ undefined , 'wonderful' ], three: [ undefined , 'result' ] } ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; ``` when the *iterator* accepts (at least) four arguments, the whole job's array or object is passed to it as the third argument. ```js var stats = createStats( 3 ) ; var myArray = [ {