UNPKG

async-kit

Version:

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

1,447 lines (1,088 loc) 136 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.async = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ /* Async Kit Copyright (c) 2014 - 2016 Cédric Ronvel The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ "use strict" ; /* global window */ if ( ! window.setImmediate ) { window.setImmediate = function( callback ) { return setTimeout( callback , 0 ) ; } ; } // Load async.js, export it, and set isBrowser to true module.exports = require( './core.js' ) ; module.exports.wrapper = require( './wrapper.js' ) ; module.exports.isBrowser = true ; var safeTimeout = require( './safeTimeout.js' ) ; module.exports.setSafeTimeout = safeTimeout.setSafeTimeout ; module.exports.clearSafeTimeout = safeTimeout.clearSafeTimeout ; },{"./core.js":2,"./safeTimeout.js":3,"./wrapper.js":4}],2:[function(require,module,exports){ (function (process,global){ /* Async Kit Copyright (c) 2014 - 2016 Cédric Ronvel The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Async flow /* TODO: - this: in all callback and event, it would be nice to bind the current execContext as 'this', so for example each jobs can access to results of others... -> DONE for callback but not for event - serialProgress() callback/event that is called for each element, but that respect sequence order even in parallel mode - config(): just set everything in one place? - dependenciesTree(): jobs are resolved using a dependencies' tree, that give arguments to transmit from one Async function to another - async.Queue: job can be added after exec(), forever, until quit() is called (it needs some brainstorming here), basicaly, some work should be done to move jobs from async.Plan to async.ExecContext - caolan/async's: compose(), detect(), filter() ? - Real async try/catch/finally, using node's Domain? - exportProxy() export a proxy function, if you call the function twice (or more) with the same arguments, the subsequent call will process immediately, replaying all callback immediately and returning, all with the same value TODO Promises: - promise() returning a Promise - Promise as a job item, or action function TODO Doc: - Jeu de piste/Organigramme en Markdown, de simples questions proposent des liens en réponse, menant sur d'autres questions avec d'autres liens de réponses, etc... à la fin, on obtient un code d'exemple qui sert de template à copier/coller. */ "use strict" ; // Load modules dependencies var NextGenEvents = require( 'nextgen-events' ) ; var treeExtend = require( 'tree-kit/lib/extend.js' ) ; var async = {} ; module.exports = async ; // Used to store important global variable, like the recursion counter (avoid stack overflow) if ( ! global.__ASYNC_KIT__ ) { global.__ASYNC_KIT__ = { recursionCounter: 0 , // Fix that to Infinity by default, until this feature is stable enough defaultMaxRecursion: Infinity } ; } ////////////////////////// // Internal Async Error // ////////////////////////// // Extend Error async.AsyncError = function AsyncError( message ) { Error.call( this ) ; Error.captureStackTrace && Error.captureStackTrace( this , this.constructor ) ; // jshint ignore:line this.message = message ; } ; async.AsyncError.prototype = Object.create( Error.prototype ) ; async.AsyncError.prototype.constructor = async.AsyncError ; ////////////////////////////////////////////////////// // Async Plan factory: create different Plan object // ////////////////////////////////////////////////////// // Empty constructor, it is just there to support instanceof operator async.Plan = function Plan() { throw new Error( "[async] Cannot create an async Plan object directly" ) ; } ; //async.Plan.prototype = Object.create( NextGenEvents.prototype ) ; async.Plan.prototype.constructor = async.Plan ; // Common properties for all instance of async.Plan var planCommonProperties = { // Configurable parallelLimit: { value: 1 , writable: true , enumerable: true , configurable: true } , raceMode: { value: false , writable: true , enumerable: true , configurable: true } , waterfallMode: { value: false , writable: true , enumerable: true , configurable: true } , waterfallTransmitError: { value: false , writable: true , enumerable: true , configurable: true } , whileAction: { value: undefined , writable: true , enumerable: true , configurable: true } , whileActionBefore: { value: false , writable: true , enumerable: true , configurable: true } , errorsAreFatal: { value: true , writable: true , enumerable: true , configurable: true } , returnMapping1to1: { value: false , writable: true , enumerable: true , configurable: true } , // Not configurable maxRecursion: { value: Infinity , writable: true , enumerable: true } , jobsData: { value: {} , writable: true , enumerable: true } , jobsKeys: { value: [] , writable: true , enumerable: true } , jobsUsing: { value: undefined , writable: true , enumerable: true } , jobsTimeout: { value: undefined , writable: true , enumerable: true } , useSafeTimeout: { value: false , writable: true , enumerable: true } , returnLastJobOnly: { value: false , writable: true , enumerable: true } , defaultAggregate: { value: undefined , writable: true , enumerable: true } , returnAggregate: { value: false , writable: true , enumerable: true } , transmitAggregate: { value: false , writable: true , enumerable: true } , usingIsIterator: { value: false , writable: true , enumerable: true } , thenAction: { value: undefined , writable: true , enumerable: true } , catchAction: { value: undefined , writable: true , enumerable: true } , finallyAction: { value: undefined , writable: true , enumerable: true } , asyncEventNice: { value: -20 , writable: true , enumerable: true } , maxRetry: { value: 0 , writable: true , enumerable: true } , retryTimeout: { value: 0 , writable: true , enumerable: true } , retryMultiply: { value: 1 , writable: true , enumerable: true } , retryMaxTimeout: { value: Infinity , writable: true , enumerable: true } , execMappingMinInputs: { value: 0 , writable: true , enumerable: true } , execMappingMaxInputs: { value: 100 , writable: true , enumerable: true } , execMappingCallbacks: { value: [ 'finally' ] , writable: true , enumerable: true } , execMappingAggregateArg: { value: false , writable: true , enumerable: true } , execMappingMinArgs: { value: 0 , writable: true , enumerable: true } , execMappingMaxArgs: { value: 101 , writable: true , enumerable: true } , execMappingSignature: { value: '( [finallyCallback] )' , writable: true , enumerable: true } , locked: { value: false , writable: true , enumerable: true } } ; // Create an async.Plan flow, parallel limit is preset to 1 (series) as default, but customizable with .parallel() async.do = function _do( jobsData ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execDoCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execDoFinal.bind( asyncPlan ) } } ) ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.do( jobsData ) ; return asyncPlan ; } ; // Create an async parallel flow async.parallel = function parallel( jobsData ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { parallelLimit: { value: Infinity , writable: true , enumerable: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execDoCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execDoFinal.bind( asyncPlan ) } } ) ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.do( jobsData ) ; return asyncPlan ; } ; // Create an async series flow async.series = function series( jobsData ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { parallelLimit: { value: 1 , enumerable: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execDoCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execDoFinal.bind( asyncPlan ) } } ) ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.do( jobsData ) ; return asyncPlan ; } ; // Create an async parallel flow, and return the result of the first non-error async.race = function race( jobsData ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { raceMode: { value: true , enumerable: true } , parallelLimit: { value: Infinity , writable: true , enumerable: true } , errorsAreFatal: { value: false , writable: true , enumerable: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execDoCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execDoFinal.bind( asyncPlan ) } } ) ; // We only want the result of the first succeeding job asyncPlan.returnLastJobOnly = true ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.do( jobsData ) ; return asyncPlan ; } ; // Create an async series flow, each job transmit its results to the next jobs async.waterfall = function waterfall( jobsData ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { waterfallMode: { value: true , enumerable: true } , waterfallTransmitError: { value: false , writable: true , enumerable: true } , parallelLimit: { value: 1 , enumerable: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execDoCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execDoFinal.bind( asyncPlan ) } } ) ; // We only want the result of the first succeeding job asyncPlan.returnLastJobOnly = true ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.do( jobsData ) ; return asyncPlan ; } ; // Create an async foreach, parallel limit is preset to 1 (series) as default, but customizable with .parallel() async.foreach = async.forEach = function foreach( jobsData , iterator ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { usingIsIterator: { value: true , writable: true , enumerable: true } , errorsAreFatal: { value: false , writable: true , enumerable: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execDoCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execDoFinal.bind( asyncPlan ) } } ) ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.do( jobsData ) ; asyncPlan.iterator( iterator ) ; return asyncPlan ; } ; // Create an async map, parallel limit is preset to Infinity, but customizable with .parallel() async.map = function map( jobsData , iterator ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { parallelLimit: { value: Infinity , writable: true , enumerable: true } , usingIsIterator: { value: true , writable: true , enumerable: true } , errorsAreFatal: { value: false , writable: true , enumerable: true } , // the result mapping should match the jobs' data 1:1 returnMapping1to1: { value: true , writable: false , enumerable: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execDoCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execDoFinal.bind( asyncPlan ) } } ) ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.do( jobsData ) ; asyncPlan.iterator( iterator ) ; return asyncPlan ; } ; // Create an async reduce, force parallel limit to 1 (does it make sense to do it in parallel?) async.reduce = function reduce( jobsData , defaultAggregate , iterator ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { parallelLimit: { value: 1 , writable: false , enumerable: true } , usingIsIterator: { value: true , writable: true , enumerable: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execDoCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execDoFinal.bind( asyncPlan ) } } ) ; if ( arguments.length < 3 ) { // No defaultAggregate given iterator = defaultAggregate ; defaultAggregate = undefined ; // Force exec signature to have an aggregateArg asyncPlan.execMappingMinInputs = 0 ; asyncPlan.execMappingMaxInputs = 100 ; asyncPlan.execMappingCallbacks = [ 'finally' ] ; asyncPlan.execMappingAggregateArg = true ; asyncPlan.execMappingMinArgs = 1 ; asyncPlan.execMappingMaxArgs = 102 ; asyncPlan.execMappingSignature = '( aggregateArg, [finallyCallback] )' ; } asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.transmitAggregate = true ; asyncPlan.returnAggregate = true ; asyncPlan.defaultAggregate = defaultAggregate ; asyncPlan.do( jobsData ) ; asyncPlan.iterator( iterator ) ; return asyncPlan ; } ; // async while // Here, simple callback is mandatory, since it should process its inputs correctly in order to loop or not async.while = function _while( whileAction ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { waterfallMode: { value: false , enumerable: true } , whileAction: { value: undefined , writable: true , enumerable: true } , whileActionBefore: { value: true , writable: false , enumerable: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execDoCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execDoFinal.bind( asyncPlan ) } } ) ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.while( whileAction ) ; return asyncPlan ; } ; // Create an async AND async.and = function and( jobsData ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { elseAction: { value: undefined , writable: true , enumerable: true } , castToBoolean: { value: false , writable: true , enumerable: true } , useLogicAnd: { value: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execLogicCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execLogicFinal.bind( asyncPlan ) } } ) ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.do( jobsData ) ; return asyncPlan ; } ; // Create an async OR (it's close to AND) async.or = function or( jobsData ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { elseAction: { value: undefined , writable: true , enumerable: true } , castToBoolean: { value: false , writable: true , enumerable: true } , useLogicAnd: { value: false } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execLogicCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execLogicFinal.bind( asyncPlan ) } } ) ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; asyncPlan.do( jobsData ) ; return asyncPlan ; } ; // Syntaxic sugar: various if notations async.if = function _if( jobsData ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { elseAction: { value: true , writable: true , enumerable: true } , castToBoolean: { value: true , writable: true , enumerable: true } , useLogicAnd: { value: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execLogicCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execLogicFinal.bind( asyncPlan ) } } ) ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; if ( jobsData ) { asyncPlan.do( jobsData ) ; } return asyncPlan ; } ; async.if.and = async.if ; async.if.or = function ifOr( jobsData ) { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { elseAction: { value: true , writable: true , enumerable: true } , castToBoolean: { value: true , writable: true , enumerable: true } , useLogicAnd: { value: false } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execLogicCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execLogicFinal.bind( asyncPlan ) } } ) ; asyncPlan.maxRecursion = global.__ASYNC_KIT__.defaultMaxRecursion ; if ( jobsData ) { asyncPlan.do( jobsData ) ; } return asyncPlan ; } ; //////////////// // Shorthands // //////////////// // Accept only one function, timeout it // async.timeout( fn , timeout , [maxRetry] , [retryTimeout] , [multiply] , [maxRetryTimeout] ) //async.timeout = function timeout( func , timeoutValue , maxRetry , retryTimeout , multiply , maxRetryTimeout ) async.callTimeout = function callTimeout( timeout , completionCallback , fn , this_ ) { if ( typeof fn !== 'function' ) { throw new Error( '[async] async.callTimeout(): argument #0 should be a function' ) ; } var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; Object.defineProperties( asyncPlan , { returnLastJobOnly: { value: true , enumerable: true } , jobsTimeout: { value: timeout , writable: true , enumerable: true } , execInit: { value: execDoInit.bind( asyncPlan ) } , execNext: { value: execDoNext.bind( asyncPlan ) } , execCallback: { value: execDoCallback } , execLoopCallback: { value: execWhileCallback } , execFinal: { value: execDoFinal.bind( asyncPlan ) } } ) ; var job = [ fn.bind( this_ ) ].concat( Array.prototype.slice.call( arguments , 4 ) ) ; asyncPlan.do( [ job ] ) ; //if ( arguments.length > 2 ) { asyncPlan.retry( maxRetry , retryTimeout , multiply , maxRetryTimeout ) ; } return asyncPlan.exec( completionCallback ) ; } ; /////////////////////// // Async Plan object // /////////////////////// // Set the job's list async.Plan.prototype.do = function _do( jobsData ) { if ( this.locked ) { return this ; } if ( jobsData && typeof jobsData === 'object' ) { this.jobsData = jobsData ; } else if ( typeof jobsData === 'function' ) { this.jobsData = [ jobsData ] ; this.returnLastJobOnly = true ; } else { this.jobsData = {} ; } // Arrays and Objects are unified, Object.keys() does the job but... this.jobsKeys = Object.keys( this.jobsData ) ; // ... we should avoid troubles with arrays that have enumerable properties if ( Array.isArray( this.jobsData ) ) { this.jobsKeys.length = this.jobsData.length ; } return this ; } ; // Set number of jobs running in parallel async.Plan.prototype.parallel = function parallel( parallelLimit ) { if ( this.locked ) { return this ; } if ( parallelLimit === undefined || parallelLimit === true ) { this.parallelLimit = Infinity ; } else if ( parallelLimit === false ) { this.parallelLimit = 1 ; } else if ( typeof parallelLimit === 'number' ) { this.parallelLimit = parallelLimit ; } return this ; } ; // Set race mode: we stop processing jobs when the first non-error job finish. // Notice: when using race() this way (without the async.race() factory), you have to call .fatal( false ) too if you want the same behaviour. async.Plan.prototype.race = function race( raceMode ) { if ( ! this.locked ) { this.raceMode = raceMode || raceMode === undefined ? true : false ; } return this ; } ; // Set waterfall mode: each job pass its results to the next job. // Be careful, this does not support parallel mode ATM, but may fail silently. // TODO: should probably raise an exception if we use parallel mode. async.Plan.prototype.waterfall = function waterfall( waterfallMode ) { if ( ! this.locked ) { this.waterfallMode = waterfallMode || waterfallMode === undefined ? true : false ; } return this ; } ; // Set while action. // Here, simple callback is mandatory, since it should process its inputs correctly in order to loop or not. // If whileActionBefore is given and truthy, then it makes a do( jobs ).while( callback , true ) the same as while( whileAction ).do( jobs ): // the while condition is evaluated before any jobs are processed. // Write this form only for non-trivial uses. async.Plan.prototype.while = function _while( whileAction , whileActionBefore ) { if ( this.locked ) { return this ; } this.whileAction = whileAction ; if ( whileActionBefore !== undefined ) { this.whileActionBefore = whileActionBefore ? true : false ; } return this ; } ; // Set the number of time to repeat the action. // It is the same as while(), provided with a simple counter function. async.Plan.prototype.repeat = function repeat( n ) { if ( this.locked ) { return this ; } var i = 0 ; if ( typeof n !== 'number' ) { n = parseInt( n ) ; } this.whileActionBefore = true ; this.whileAction = function( error , results , callback ) { // callback should be called last, to avoid sync vs async mess, hence i++ come first, and we check i<=n rather than i<n i ++ ; callback( i <= n ) ; } ; return this ; } ; // Set if errors are fatal or not async.Plan.prototype.fatal = function fatal( errorsAreFatal ) { if ( ! this.locked ) { this.errorsAreFatal = errorsAreFatal || errorsAreFatal === undefined ? true : false ; } return this ; } ; // Cast logic jobs to boolean async.Plan.prototype.boolean = function boolean( castToBoolean ) { if ( ! this.locked ) { this.castToBoolean = castToBoolean || castToBoolean === undefined ? true : false ; } return this ; } ; // Transmit error, in waterfall mode async.Plan.prototype.transmitError = function transmitError( waterfallTransmitError ) { if ( ! this.locked ) { this.waterfallTransmitError = waterfallTransmitError || waterfallTransmitError === undefined ? true : false ; } return this ; } ; // Set the timeout for each jobs, the callback will be called with an async error for each of them that timeout async.Plan.prototype.timeout = function timeout( jobsTimeout ) { if ( ! this.locked ) { if ( typeof jobsTimeout === 'number' ) { this.jobsTimeout = jobsTimeout ; } else { this.jobsTimeout = undefined ; } } return this ; } ; // Set the 'safeTimeout' mode for all internal timeout async.Plan.prototype.safeTimeout = function safeTimeout( useSafeTimeout ) { if ( ! this.locked ) { this.useSafeTimeout = useSafeTimeout === undefined ? true : !! useSafeTimeout ; } } ; // Set how to retry jobs in error async.Plan.prototype.retry = function retry( maxRetry , timeout , multiply , maxTimeout ) { if ( this.locked ) { return this ; } if ( typeof maxRetry === 'number' ) { this.maxRetry = maxRetry ; } if ( typeof timeout === 'number' ) { this.retryTimeout = timeout ; } if ( typeof multiply === 'number' ) { this.retryMultiply = multiply ; } if ( typeof maxTimeout === 'number' ) { this.retryMaxTimeout = maxTimeout ; } return this ; } ; // Set if only the last job's results should be passed to the callback async.Plan.prototype.lastJobOnly = function lastJobOnly( returnLastJobOnly ) { if ( ! this.locked ) { this.returnLastJobOnly = returnLastJobOnly || returnLastJobOnly === undefined ? true : false ; } return this ; } ; // Set if the result mapping should match the jobs' data 1:1 async.Plan.prototype.mapping1to1 = function mapping1to1( returnMapping1to1 ) { if ( ! this.locked ) { this.returnMapping1to1 = returnMapping1to1 || returnMapping1to1 === undefined ? true : false ; } return this ; } ; // Set the performer of the jobs: if set, do() is not feeded by callback but by arguments for this single callback function. // The performer function should accept a callback as its last argument, in the nodejs' way. async.Plan.prototype.using = function using( jobsUsing ) { if ( ! this.locked ) { this.jobsUsing = jobsUsing ; } return this ; } ; // Same as using(), but the given function receive an uniq "element" containing the whole job as its first argument: // it is like using().usingIterator(), a behaviour similar to the async.foreach() factory async.Plan.prototype.iterator = function iterator( iterator_ ) { if ( this.locked ) { return this ; } this.jobsUsing = iterator_ ; this.usingIsIterator = true ; return this ; } ; // Transmit aggregate, for aggregator mode (reduce, etc) async.Plan.prototype.aggregator = function aggregator( transmitAggregate , returnAggregate , defaultAggregate ) { if ( ! this.locked ) { return this ; } this.transmitAggregate = transmitAggregate || transmitAggregate === undefined ? true : false ; this.returnAggregate = returnAggregate || returnAggregate === undefined ? true : false ; if ( arguments.length > 2 ) { this.defaultAggregate = defaultAggregate ; } return this ; } ; // Set if using() is an iterator (like async.foreach()), if so, the whole job is transmitted as one argument rather than an argument list // NODOC async.Plan.prototype.usingIterator = function usingIterator( usingIsIterator ) { if ( ! this.locked ) { this.usingIsIterator = usingIsIterator || usingIsIterator === undefined ? true : false ; } return this ; } ; // Set the async'ness of the flow, even sync jobs can be turned async async.Plan.prototype.nice = function nice( asyncEventNice ) { if ( this.locked ) { return this ; } if ( asyncEventNice === undefined || asyncEventNice === null || asyncEventNice === true ) { this.asyncEventNice = -1 ; } else if ( asyncEventNice === false ) { this.asyncEventNice = -20 ; } else { this.asyncEventNice = asyncEventNice ; } return this ; } ; // Set the async'ness of the flow, even sync jobs can be turned async async.Plan.prototype.setMaxRecursion = function setMaxRecursion( maxRecursion ) { if ( this.locked ) { return this ; } if ( maxRecursion >= 0 ) { this.maxRecursion = maxRecursion ; } return this ; } ; // Set action to do on completion. // If catch() or else() are present and match, then() is not triggered. async.Plan.prototype.then = function then( thenAction ) { if ( ! this.locked ) { this.thenAction = thenAction ; } return this ; } ; // Set action to do on logical false status async.Plan.prototype.else = function _else( elseAction ) { if ( ! this.locked ) { this.elseAction = elseAction || true ; } return this ; } ; // Set action to do on error async.Plan.prototype.catch = function _catch( catchAction ) { if ( ! this.locked ) { this.catchAction = catchAction || true ; } return this ; } ; // Set action to do, that trigger whether it has triggered or not any of then()/catch()/else() async.Plan.prototype.finally = function _finally( finallyAction ) { if ( ! this.locked ) { this.finallyAction = finallyAction || true ; } return this ; } ; // Return a clone of the object async.Plan.prototype.clone = function clone() { var asyncPlan = Object.create( async.Plan.prototype , planCommonProperties ) ; treeExtend( null , asyncPlan , this ) ; asyncPlan.locked = false ; return asyncPlan ; } ; // Export the async.Plan object as an async function, so it can be called later at will async.Plan.prototype.export = function _export( execMethod ) { switch ( execMethod ) { case 'execFinally' : return this.clone().execFinally.bind( this ) ; case 'execThenCatch' : return this.clone().execThenCatch.bind( this ) ; case 'execThenElse' : return this.clone().execThenElse.bind( this ) ; case 'execThenElseCatch' : return this.clone().execThenElseCatch.bind( this ) ; case 'execArgs' : return this.clone().execArgs.bind( this ) ; case 'execKV' : return this.clone().execKV.bind( this ) ; default : return this.clone().exec.bind( this ) ; } } ; // This is the common exec() function, its arguments can be mapped using execMapping() async.Plan.prototype.exec = function exec() { var config = { inputs: [] , callbacks: {} } , offset = 0 , i ; if ( arguments.length < this.execMappingMinArgs ) { throw new Error( "[async] Too few arguments, in this instance, the function signature is: fn" + this.execMappingSignature ) ; } else if ( arguments.length > this.execMappingMaxArgs ) { throw new Error( "[async] Too much arguments, in this instance, the function signature is: fn" + this.execMappingSignature ) ; } if ( this.execMappingAggregateArg ) { offset ++ ; config.aggregate = arguments[ 0 ] ; } if ( this.execMappingMinInputs === this.execMappingMaxInputs ) { // Fixed arguments count, variable callback count possible config.inputs = Array.prototype.slice.call( arguments , offset , this.execMappingMaxInputs + offset ) ; for ( i = 0 ; i < this.execMappingCallbacks.length && config.inputs.length + i < arguments.length ; i ++ ) { config.callbacks[ this.execMappingCallbacks[ i ] ] = arguments[ config.inputs.length + offset + i ] ; } } else { // Variable arguments count, fixed callback count config.inputs = Array.prototype.slice.call( arguments , offset , - this.execMappingCallbacks.length ) ; for ( i = 0 ; i < this.execMappingCallbacks.length ; i ++ ) { config.callbacks[ this.execMappingCallbacks[ i ] ] = arguments[ config.inputs.length + offset + i ] ; } } return this.execInit( config ) ; } ; // Exec templates async.Plan.prototype.execFinally = function execFinally( finallyCallback ) { return this.execInit( { inputs: [] , callbacks: { 'finally': finallyCallback } } ) ; } ; async.Plan.prototype.execThenCatch = function execThenCatch( thenCallback , catchCallback , finallyCallback ) { return this.execInit( { inputs: [] , callbacks: { 'then': thenCallback , 'catch': catchCallback , 'finally': finallyCallback } } ) ; } ; async.Plan.prototype.execThenElse = function execThenElse( thenCallback , elseCallback , finallyCallback ) { return this.execInit( { inputs: [] , callbacks: { 'then': thenCallback , 'else': elseCallback , 'finally': finallyCallback } } ) ; } ; async.Plan.prototype.execThenElseCatch = function execThenElseCatch( thenCallback , elseCallback , catchCallback , finallyCallback ) { return this.execInit( { inputs: [] , callbacks: { 'then': thenCallback , 'else': elseCallback , 'catch': catchCallback , 'finally': finallyCallback } } ) ; } ; async.Plan.prototype.execArgs = function execArgs() { return this.execInit( { inputs: arguments , callbacks: {} } ) ; } ; // Configure the inputs of exec() function // .callbacks // .minInputs // .maxInputs // .inputsName // .aggregateArg async.Plan.prototype.execMapping = function execMapping( config ) { if ( this.locked ) { return this ; } config = treeExtend( null , { minInputs: 0 , maxInputs: 0 } , config ) ; var i , j , maxUnnamed = 5 ; config.minInputs = parseInt( config.minInputs ) ; config.maxInputs = parseInt( config.maxInputs ) ; if ( config.minInputs < config.maxInputs ) { this.execMappingMinInputs = config.minInputs ; this.execMappingMaxInputs = config.maxInputs ; } else { // User is stOopid, swap... this.execMappingMinInputs = config.maxInputs ; this.execMappingMaxInputs = config.minInputs ; } this.execMappingCallbacks = Array.isArray( config.callbacks ) ? config.callbacks : [] ; this.execMappingInputsName = Array.isArray( config.inputsName ) ? config.inputsName : [] ; this.execMappingSignature = '( ' ; if ( this.execMappingMinInputs === this.execMappingMaxInputs ) { // Fixed input count, variable callback count possible this.execMappingMinArgs = this.execMappingMinInputs ; this.execMappingMaxArgs = this.execMappingMaxInputs + this.execMappingCallbacks.length ; if ( config.aggregateArg ) { this.execMappingAggregateArg = config.aggregateArg ; this.execMappingMinArgs ++ ; this.execMappingMaxArgs ++ ; this.execMappingSignature += 'aggregateValue' ; } for ( i = 0 ; i < this.execMappingMaxInputs ; i ++ ) { if ( i > 0 || config.aggregateArg ) { this.execMappingSignature += ', ' ; } if ( i >= maxUnnamed && typeof this.execMappingInputsName[ i ] !== 'string' ) { this.execMappingSignature += '... ' ; break ; } this.execMappingSignature += typeof this.execMappingInputsName[ i ] === 'string' ? this.execMappingInputsName[ i ] : 'arg#' + ( i + 1 ) ; } for ( j = 0 ; j < this.execMappingCallbacks.length ; j ++ ) { if ( i + j > 0 || config.aggregateArg ) { this.execMappingSignature += ', ' ; } this.execMappingSignature += '[' + this.execMappingCallbacks[ j ] + 'Callback]' ; } } else { // Variable input count, fixed callback count this.execMappingMinArgs = this.execMappingMinInputs + this.execMappingCallbacks.length ; this.execMappingMaxArgs = this.execMappingMaxInputs + this.execMappingCallbacks.length ; if ( config.aggregateArg ) { this.execMappingAggregateArg = config.aggregateArg ; this.execMappingMinArgs ++ ; this.execMappingMaxArgs ++ ; this.execMappingSignature += 'aggregateValue' ; } for ( i = 0 ; i < this.execMappingMaxInputs ; i ++ ) { if ( i > 0 || config.aggregateArg ) { this.execMappingSignature += ', ' ; } if ( i < this.execMappingMinInputs ) { if ( i >= maxUnnamed && typeof this.execMappingInputsName[ i ] !== 'string' ) { this.execMappingSignature += '... ' ; break ; } this.execMappingSignature += typeof this.execMappingInputsName[ i ] === 'string' ? this.execMappingInputsName[ i ] : 'arg#' + ( i + 1 ) ; } else { if ( i >= maxUnnamed && typeof this.execMappingInputsName[ i ] !== 'string' ) { this.execMappingSignature += '[...] ' ; break ; } this.execMappingSignature += '[' + ( typeof this.execMappingInputsName[ i ] === 'string' ? this.execMappingInputsName[ i ] : 'arg#' + ( i + 1 ) ) + ']' ; } } for ( j = 0 ; j < this.execMappingCallbacks.length ; j ++ ) { if ( i + j > 0 || config.aggregateArg ) { this.execMappingSignature += ', ' ; } this.execMappingSignature += this.execMappingCallbacks[ j ] + 'Callback' ; } } this.execMappingSignature += ' )' ; return this ; } ; // More sage and deterministic exec(), with all arguments given into a single object async.Plan.prototype.execKV = function execKV( config ) { if ( config.inputs === undefined ) { config.inputs = [] ; } else if ( ! Array.isArray( config.inputs ) ) { config.inputs = [ config.inputs ] ; } if ( config.callbacks === undefined || typeof config.callbacks !== 'object' ) { config.callbacks = {} ; } if ( config.then ) { config.callbacks.then = config.then ; } if ( config.else ) { config.callbacks.else = config.else ; } if ( config.catch ) { config.callbacks.catch = config.catch ; } if ( config.finally ) { config.callbacks.finally = config.finally ; } // Nothing to do here, user is free to pass whatever is needed //if ( config.aggregate === undefined ) { config.aggregate = null ; } return this.execInit( config ) ; } ; // Internal, what to do on new loop iteration async.Plan.prototype.execLoop = function execLoop( fromExecContext ) { return this.execInit( {} , fromExecContext ) ; } ; // Internal exec of callback-like job/action async.Plan.prototype.execJob = function execJob( execContext , job , indexOfKey , tryIndex ) { var self = this , args , key = execContext.jobsKeys[ indexOfKey ] ; // Create the job's context var jobContext = Object.create( async.JobContext.prototype , { execContext: { value: execContext , enumerable: true } , indexOfKey: { value: indexOfKey , enumerable: true } , tryIndex: { value: tryIndex , enumerable: true } , aborted: { value: false , writable: true , enumerable: true } , abortedLoop: { value: false , writable: true , enumerable: true } } ) ; // Add the callback to the context Object.defineProperty( jobContext , 'callback' , { value: this.execCallback.bind( this , jobContext ) , enumerable: true } ) ; // Also add the jobContext into the bounded function: it's an alternate way to access a job's context. Object.defineProperty( jobContext.callback , 'jobContext' , { value: jobContext , enumerable: true } ) ; // Set the current job's status to 'pending' execContext.jobsStatus[ key ].status = 'pending' ; execContext.jobsStatus[ key ].tried ++ ; // Set up the nice value? For instance only syncEmit() are used //jobContext.setNice( this.asyncEventNice ) ; if ( typeof this.jobsUsing === 'function' ) { if ( this.usingIsIterator ) { if ( this.transmitAggregate ) { if ( this.jobsUsing.length <= 3 ) { this.jobsUsing.call( jobContext , execContext.aggregate , job , jobContext.callback ) ; } else if ( this.jobsUsing.length <= 4 ) { this.jobsUsing.call( jobContext , execContext.aggregate , job , Array.isArray( execContext.jobsData ) ? indexOfKey : key , jobContext.callback ) ; } else { this.jobsUsing.call( jobContext , execContext.aggregate , job , Array.isArray( execContext.jobsData ) ? indexOfKey : key , execContext.jobsData , jobContext.callback ) ; } } else { if ( this.jobsUsing.length <= 2 ) { this.jobsUsing.call( jobContext , job , jobContext.callback ) ; } else if ( this.jobsUsing.length <= 3 ) { this.jobsUsing.call( jobContext , job , Array.isArray( execContext.jobsData ) ? indexOfKey : key , jobContext.callback ) ; } else { this.jobsUsing.call( jobContext , job , Array.isArray( execContext.jobsData ) ? indexOfKey : key , execContext.jobsData , jobContext.callback ) ; } } } else if ( Array.isArray( job ) ) { args = job.slice() ; if ( this.transmitAggregate ) { args.unshift( execContext.aggregate ) ; } args.push( jobContext.callback ) ; this.jobsUsing.apply( jobContext , args ) ; } else { this.jobsUsing.call( jobContext , job , jobContext.callback ) ; } } else if ( typeof job === 'function' ) { if ( this.waterfallMode && indexOfKey > 0 ) { // remove the first, error arg if waterfallTransmitError is false //console.log( index , key , execContext.results ) ; args = execContext.results[ execContext.jobsKeys[ indexOfKey - 1 ] ].slice( this.waterfallTransmitError ? 0 : 1 ) ; args.push( jobContext.callback ) ; job.apply( jobContext , args ) ; } else if ( Array.isArray( this.jobsUsing ) || this.execMappingMaxInputs ) { if ( Array.isArray( this.jobsUsing ) ) { args = treeExtend( null , [] , this.jobsUsing , execContext.execInputs ) ; } else { args = treeExtend( null , [] , execContext.execInputs ) ; } args.push( jobContext.callback ) ; job.apply( jobContext , args ) ; } else { job.call( jobContext , jobContext.callback ) ; } } else if ( Array.isArray( job ) && typeof job[ 0 ] === 'function' ) { args = job.slice( 1 ) ; args.push( jobContext.callback ) ; job[ 0 ].apply( jobContext , args ) ; } else if ( typeof job === 'object' && job instanceof async.Plan ) { // What to do with jobUsing and execContext.execInputs here? Same as if( typeof job === 'function' ) ? job.exec( jobContext.callback ) ; } else { this.execCallback.call( this , jobContext ) ; return this ; } // Timers management if ( execContext.jobsTimeoutTimers[ key ] !== undefined ) { clearTimeout( execContext.jobsTimeoutTimers[ key ] ) ; execContext.jobsTimeoutTimers[ key ] = undefined ; } if ( execContext.retriesTimers[ key ] !== undefined ) { clearTimeout( execContext.retriesTimers[ key ] ) ; execContext.retriesTimers[ key ] = undefined ; } if ( typeof this.jobsTimeout === 'number' && this.jobsTimeout !== Infinity ) { execContext.jobsTimeoutTimers[ key ] = setTimeout( function() { execContext.jobsTimeoutTimers[ key ] = undefined ; execContext.jobsStatus[ key ].status = 'timeout' ; jobContext.emit( 'timeout' ) ; self.execCallback.call( self , jobContext , new async.AsyncError( 'jobTimeout' ) ) ; } , this.jobsTimeout ) ; } return this ; } ; // Internal exec of callback-like action async.Plan.prototype.execAction = function execAction( execContext , action , args ) { // call the matching action if ( typeof action === 'function' ) { action.apply( execContext , args ) ; } else if ( typeof action === 'object' && action instanceof async.Plan ) { action.exec() ; } } ; ///////////////////////////////////////////////////////////////////////// // Async JobContext: Context of a job execution, transmitted as *this* // ///////////////////////////////////////////////////////////////////////// // Empty constructor, it is just there to support instanceof operator async.JobContext = function JobContext() { throw new Error( "[async] Cannot create an async JobContext object directly" ) ; } ; // Extends it from EventEmitter async.JobContext.prototype = Object.create( NextGenEvents.prototype ) ; async.JobContext.prototype.constructor = async.JobContext ; // Permit a userland-side abort of the job's queue async.JobContext.prototype.abort = function abort() { this.aborted = true ; this.callback.apply( undefined , arguments ) ; } ; // Permit a userland-side abort of the job's queue, and event the whole loop async.JobContext.prototype.abortLoop = function abortLoop() { this.aborted = true ; this.abortedLoop = true ; this.callback.apply( undefined , arguments ) ; } ; ////////////////////////////////////////////////// // Async ExecContext: Context of plan execution // ////////////////////////////////////////////////// // Empty constructor, it is just there to support instanceof operator async.ExecContext = function ExecContext() { throw new Error( "[async] Cannot create an async ExecContext object directly" ) ; } ; // Extends it from EventEmitter async.ExecContext.prototype = Object.create( NextGenEvents.prototype ) ; async.ExecContext.prototype.constructor = async.ExecContext ; // This is used to complete jobsStatus only on-demand, so big data that are not object (e.g. big string) // does not get duplicated for nothing async.ExecContext.prototype.getJobsStatus = function getJobsStatus() { var i , key , fullJobsStatus = Array.isArray( this.jobsData ) ? [] : {} ; for ( i = 0 ; i < this.jobsKeys.length ; i ++ ) { key = this.jobsKeys[ i ] ; fullJobsStatus[ key ] = treeExtend( null , { job: this.jobsData[ key ] , result: this.results[ key ] } , this.jobsStatus[ key ] ) ; } return fullJobsStatus ; } ; function execDoInit( config , fromExecContext ) { var i , execContext , isArray = Array.isArray( this.jobsData ) ; if ( fromExecContext && fromExecContext.whileIterator === -1 ) { // This is a async.while().do() construct, reuse the parent context execContext = fromExecContext ; execContext.whileIterator = 0 ; } else { // Create instanceof ExecContext execContext = Object.create( async.ExecContext.prototype , { plan: { value: this } , aggregate: { value: ( 'aggregate' in config ? config.aggregate : this.defaultAggregate ) , writable: true , enumerable: true } , results: { value: ( isArray ? [] : {} ) , writable: true , enumerable: true } , result: { value: undefined , writable: true , enumerable: true } , // Conditionnal version jobsTimeoutTimers: { value: ( isArray ? [] : {} ) , writable: true } , jobsStatus: { value: ( isArray ? [] : {} ) , writable: true , enumerable: true } , retriesTimers: { value: ( isArray ? [] : {} ) , writable: true } , retriesCounter: { value: ( isArray ? [] : {} ) , writable: true , enumerable: true } , tryUserResponseCounter: { value: ( isArray ? [] : {} ) , writable: true , enumerable: true } , tryResponseCounter: { value: ( isArray ? [] : {} ) , writable: true , enumerable: true } , iterator: { value: 0 , writable: true , enumerable: true } , pending: { value: 0 , writable: true , enumerable: true } , resolved: { value: 0 , writable: true , enumerable: true } , ok: { value: 0 , writable: true , enumerable: true } , failed: { value: 0 , writable: true , enumerable: true } , status: { value: undefined , writable: true , enumerable: true } , error: { value: undefined , writable: true , enumerable: true } , statusTriggerJobsKey: { value: undefined , writable: true , enumerable: true } , whileStatus: { value: undefined , writable: true } , // true if current execContext has looped in another execContext (one loop per execContext possible) // false if this execContext will never loop, undefined if this isn't settled whileChecked: { value: false , writable: true } } ) ; // Add some properties depending on inherited ExecContext or not if ( ! fromExecContext ) { // This is the top-level/first ExecContext Object.defineProperties( execContext , { root: { value: execContext , enumerable: true } , jobsData: { value: ( isArray ? this.jobsData.slice(0) : treeExtend( null , {} , this.jobsData ) ) , enumerable: true } , jobsKeys: { value: this.jobsKeys.slice(0) , enumerable: true } , execInputs: { value: config.inputs , enumerable: true } , execCallbacks: { value: config.callbacks } , whileIterator: { value: 0 , enumerable: true , writable: true } } ) ; } else { // This is a loop, and this ExecContext is derived from the first one Object.defineProperties( execContext , { root: { value: fromExecContext.root , enumerable: true } , jobsData: { value: fromExecContext.jobsData , enumerable: true } , jobsKeys: { value: fromExecContext.jobsKeys , enumerable: true } , execInputs: { value: fromExecContext.execInputs , enumerable: true } , execCallbacks: { value: fromExecContext.execCallbacks } , whileIterator: { value: fromExecContext.whileIterator + 1 , enumerable: true , writable: true } } ) ; } // Add more properties depending on previous properties Object.defineProperties( execContext , { waiting: { value: execContext.jobsKeys.length , writable: true , enumerable: true } } ) ; // Init the jobsStatus for ( i = 0 ; i < execContext.jobsK