async-kit
Version:
A simple and powerful async abstraction lib for easily writing Node.js code.
1,548 lines (1,334 loc) • 114 kB
Markdown
# 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 = [
{