define([ 'dojo/_base/array', 'dojo/_base/lang', 'dojo/when', 'dojo/Deferred' ],function(array,lang,when,Deferred){ /** Execute possible async functions in sequence * Execute functions in order. When a function returns a Promise, * the next function is only executed when the promise resolves. * If the function returns a value, the next function is executed * synchronously. The return value of every function is passed as argument * to the next. * Returns the return value of the last function in the chain. */ function seq(functions,ctx) { var d = new Deferred(); var cancelled = false; var running = null; function cancel(err) { if ( cancelled ) { return; } cancelled = true; if ( running && running.cancel && !running.isFulfilled() ) { running.cancel(); } d.reject(err); } function update(fs,arg) { if ( cancelled ) { return; } if ( fs.length > 0 ) { try { var f = fs.shift(); running = when(f.call(ctx,arg)) .then(function(res){ update(fs,res); },function(err){ cancel(err); }); } catch(err) { cancel(err); } } else { d.resolve(arg); } } update(functions); return d.promise; } /** Execute possible async functions in parallel * Execute all functions in parallel, resolve only when all have resolved. * NB. When a function is not async, it will fully complete before the next * async function is started. If you mix async and non async functions, put * the sync functions last to fully exploit the async ones. * Return an array with all the return values of the functions. */ function par(functions,ctx) { var d; // later: Deferred var cancelled = false; var running = []; function cancel(err) { if (cancelled) { return; } cancelled = true; array.forEach(running,function(running){ if ( running && running.cancel && !running.isFulfilled() ) { running.cancel(); } }); d.reject(err); } d = new Deferred(cancel); var results = []; var left = functions.length; function update(res,idx) { if (cancelled) { return; } results[idx] = res; left -= 1; // Works because/as long as AJAX/JS is single-threaded. if ( left === 0 ) { d.resolve(results); } } array.forEach(functions,function(f,idx){ if (cancelled) { return; } try { running.push(when(f.call(ctx)).then(function(res){ update(res,idx); },function(err){ cancel(err); })); } catch (err) { cancel(); } }); return d.promise; } /** Iterate over an array with async callback * Execute the callback and if it's async, wait until it's finished before * executing the next one. */ function forEach(list,callback,ctx) { var fs = array.map(list,function(item,index,items){ return lang.hitch(ctx,callback,item,index,items); }); return seq(fs); } /** Map a list, possible async * Map every element of a list, returning a promise to the mapped list. */ function map(list,callback,ctx) { var fs = array.map(list,function(item,index,items){ return lang.hitch(ctx,callback,item,index,items); }); return par(fs); } return { seq: seq, par: par, forEach: forEach, map: map }; });