Modular and fast Promises implementation
Implementation originally inspired by Kris Kowal's Q
With Deferred you also can: Process collections of deferred calls. Handle Node.js asynchronous functions. Limit concurrency of scheduled tasks. Emit progress events or stream results partially on the go.
If you need help with deferred, please ask on dedicated mailing list: firstname.lastname@example.org
var fs = require'fs';var readdir = fsreaddir;var readFile = fsreadFile;var writeFile = fswriteFile;// Read all filenames in given pathreaddir__dirnamevar result waiting;if err// if we're unable to get file listing throw errorthrow err;// Filter *.js files and generated lib.jsfiles = filesfilterreturn fileslice-3 === '.js' && file !== 'lib.js';;// Read content of each filewaiting = 0;result = ;filesforEach++waiting;readFilefileif err// We were not able to read file content, throw errorthrow err;resultindex = content;if !--waiting// Got content of all files// Concatenate into one string and write into lib.jswriteFile__dirname + '/lib.js' resultjoin"\n"if err// We cannot write lib.js file, throw errorthrow err;;;;;
var promisify = require'deferred'promisify;var fs = require'fs';// Convert node.js async functions, into ones that return a promisevar readdir = promisifyfsreaddir;var readFile = promisifyfsreadFile 1; // Restrict arity to 1 + callbackvar writeFile = promisifyfswriteFile;writeFile__dirname + '/lib.js'// Read all filenames in given pathreaddir__dirname// Filter *.js files and generated lib.jsinvoke'filter'return fileslice-3 === '.js' && file !== 'lib.js';// Read content of all filesmapreadFile// Concatenate files content into one stringinvoke'join' '\n'done; // If there was any error on the way throw it
See examples folder for a demonstration of promises usage in some other real world cases.
In your project path:
$ npm install deferred
$ npm install -g webmake$ git clone git://github.com/medikoo/deferred.git$ cd deferred$ npm install$ cd ..$ webmake --name=deferred deferred/index.js deferred.js
Last command bundles deferred with all it's functionalities, but you may need just a subset, you can have that by addressing specific modules directly, e.g. with following you will build just core functionality with map extension:
$ webmake --name=deferred --include=deferred/ext/promise/map.js deferred/deferred.js deferred.js
If you work with AMD modules, use amd option, so generated bundle is one:
$ webmake --amd deferred/index.js deferred.js
Mind that deferred relies on some ECMAScript5 features, so for older browsers you need to load as well es5-shim
For work that doesn't return immediately (asynchronous) you may create deferred object. Deferred holds both
promise objects. Observers interested in value are attached to
promise object, with
resolve we resolve promise with an actual value. In common usage
promise is returned to the world and
resolve is kept internally
delay function decorator:
var deferred = require'deferred';varreturnvar def = deferred self = this args = arguments;setTimeoutvar value;tryvalue = fnapplyself args;catch edefrejecte;return;defresolvevalue;timeout;return defpromise;;;var delayedAdd = delayreturn a + b;100;var resultPromise = delayedAdd2 3;console.logdeferredisPromiseresultPromise; // trueresultPromise// Invoked after 100 millisecondsconsole.logvalue; // 5;
Promise is an object that represents eventual value which may already be available or is expected to be available in a future. Promise may succeed (fulfillment) or fail (rejection). Promise can be resolved only once.
deferred (and most of the other promise implementations) you may listen for the value by passing observers to
In deferred promise is really a
then function, so you may use promise function directly:
promise === promisethen; // truepromiseonsuccess onfail;
If you want to keep clear visible distinction between promises and other object I encourage you to always use
onfail are optional. They will be called only once and only either
onfail will be called.
Promises by nature can be chained.
promise function returns another promise which is resolved with a value returned by a callback function:
delayedAdd2 3return result * resultconsole.logresult; // 25;
undefined which will be treated as no value). Going that way you may override result of a promise chain with specific value.
Promises can be nested. If a promise resolves with another promise, it's not really resolved. It's resolved only when final promise is resolved with a real value:
var def = deferred;defresolvedelayedAdd2 3; // Resolve promise with another promisedefpromiseconsole.logresult; // 5;;
Errors in promises are handled with separate control flow, that's one of the reasons why code written with promises is more readable and maintainable than when using callbacks approach.
A promise resolved with an error (rejected), propagates its error to all promises that depend on this promise (e.g. promises initiated by adding observers).
If observer function crashes with error or returns error, its promise is rejected with the error.
To handle error, pass dedicated callback as second argument to promise function:
delayedAdd2 3throw 'Error!'// never called// handle error;;
To expose the errors that are not handled, end promise chain with
.done(), then error that broke the chain will be thrown:
delayedAdd2 3throw 'Error!'// never executeddone; // throws error!
It's important to end your promise chains with
done otherwise eventual ignored errors will not be exposed.
done function is same as for
then (or promise itself)
done is aliased with
end function, however
end will be removed with introduction of v0.7 release.
promise// processdone// process result// handle error;
And as with
then either callback can be provided. If callback for error was omitted, eventual error will be thrown.
You may create initially resolved promises.
var promise = deferred1;promiseconsole.logresult; // 1;;
There is a known convention (coined by Node.js) for working with asynchronous calls. An asynchronous function receives a callback argument which handles both eventual error and expected value:
var fs = require'fs';fsreadFile__filename 'utf-8'if err// handle error;return;// process content;
It's not convenient to work with both promises and callback style functions. When you decide to build your flow with promises don't mix both concepts, just
promisify asynchronous functions so they return promises instead.
var deferred = require'deferred'fs = require'fs'readFile = deferredpromisifyfsreadFile;readFile__filename 'utf-8'// process content// handle error;
promisify accepts also second argument, through which we may specify length of arguments that function takes (not counting callback argument), it may be handy if there's a chance that unexpected arguments will be passed to function (e.g. Array's
promisify also takes care of input arguments. It makes sure that all arguments that are to be passed to asynchronous function are first resolved.
If for some reason you need to turn asynchronous functions into ones that return promises, inline in algorithm, then
callAsync is for you.
Still mind that
promisify is much better (cleaner) choice if it's possible to prepare reusable wrapper upfront.
var callAsync = require'deferred'callAsync;callAsyncsomeAsyncFn context arg1 arg2done// process result;
If you need to turn asynchronous methods to ones that return promises, and you prefer not to augment its class prototypes,
invokeAsync addresses that use case.
var invokeAsync = require'deferred'invokeAsyncinvokeAsyncdb 'find' 'books' title: "Some title" done// process result;
When we're interested in results of more than one promise object we may group them into one promise with
deferreddelayedAdd2 3 delayedAdd3 5 delayedAdd1 7console.logresult; // [5, 8, 8];
It's analogous to Array's map, with that difference that it returns promise (of an array) that would be resolved when promises for all items are resolved. Any error that would occur will reject the promise and resolve it with same error.
In following example we take content of each file found in an array:
var readFile = deferredpromisifyfsreadFile;deferredmapfilenamesreturn readFilefilename 'utf-8';// result is an array of file's contents;
map is also available directly on a promise object, so we may invoke it directly on promise of a collection.
Let's try again previous example but this time instead of relying on already existing filenames, we take list of files from current directory:
var readdir = deferredpromisifyfsreaddir;var readFile = deferredpromisifyfsreadFile;readdir__dirnamemapreturn readFilefilename 'utf-8';// result is an array of file's contents;
This function is available also as an extension on promise object.
See limiting concurrency section for info on how to limit maximum number of concurrent calls in
It's same as Array's reduce with that difference that it calls callback only after previous accumulated value is resolved, this way we may accumulate results of collection of promises or invoke some asynchronous tasks one after another.
deferredreducedelayedAdd2 3 delayedAdd3 5 delayedAdd1 7return delayedAdda b;console.logresult; // 21;
This function is available also as an extension on promise object.
Promise aware Array's some. Process collection one after another and stop when first item matches your criteria
deferredsomefilename1 filename2 filename3return readFilefilename 'utf8'if dataindexOf'needle'// Got it! Stop further processingreturn true;;;
This function is available also as an extension on promise object.
There are cases when we don't want to run too many tasks simultaneously. Like common case in Node.js when we don't want to open too many file descriptors.
Handle that with
deferred.gate, it wraps functions that return promises. It doesn't do anything to promise objects, it just limits creation of them by blocking calls to function it wraps.
var fn = deferredgatevar def = deferred;// ..return defpromise;10;
If there are already 10 concurrent tasks running
async function invocation will be postponed into the queue and released when first of the running tasks will finish its job.
Additionally with third argument, we may limit number of postponed calls, so if there's more than n of them rest is discarded. In below example, queue holds maximum 3 postponed calls, rest will be discarded.
var fn = deferredgate 10 3;
In following example we'll limit concurrent readFile calls when using deferred.map:
// Open maximum 100 file descriptors at oncedeferredmapfilenames deferredgatereturn readFilefilename 'utf-8';100// result is an array of file's contents;
Promise objects are also an event emitters. Deferred implementation is backed by cross-environment event-emitter solution
Simple Ajax file uploader example:
varvar def = deferred;var xhr = ;xhropen'POST' url true;xhronload = defresolve;defresolve"Could not upload files";;defpromiseemit'progress' e;;xhrsenddata;return defpromise;;var upload = ajaxFileUploaderformData;uploadon'progress'// process progress events;uploaddone// All files uploaded!;
Another use case would be to provide obtained data partially on the go (stream like). Imagine recursive directory reader that scans whole file system and provides filenames as it approaches them:
var reader = readdirDeeprootPath; // reader promise is returnedreaderon'data'// Called many times during scan with obtained names;readerdone// File-system scan finished!;
Promise objects are equipped with some useful extensions. All extension are optional but are loaded by default when
deferred is loaded via
When preparing client-side file (with help of e.g. modules-webmake) you are free to decide, which extensions you want to take (see source of
lib/index.js on how to do that)
Third brother of
done. Has same signature but neither extends chain nor ends it, instead splits it by returning promise on which it was invoked. Useful when we want to return promise, but on a side (in parallel) do something else with obtained value:
var x = deferred foo: 'bar' ;var promise = deferred foo: 'bar' ;var y = promiseasideconsole.logvalue === x; // true;console.logy === promise; // true
then but accepts only
var def = deferred promise2;promise2 = defpromisecatchreturn 'Never mind';;defreject"Error";promise2doneconsole.logvalue; // Prints "Never mind";
Convert back to callback style. Useful if you want to process regular callback at the end of promise chain. Simple use case would be regular asynchronous function built internally with promises.
cb also makes sure that your callback is not called immediately but in next tick earliest.
With cb we may build hybrid functions that do both, handle asynchronous callback and return promise:
varreturn someAsyncProcessingThatReturnsPromisex ycbcallback;;
Invokes given callback when promise is either fulfilled or rejected
varprepare;promise = asyncFn;promisefinallycleanup;
To directly get to object property use
var promise = deferred foo: 'bar' ;promiseconsole.logobjfoo; // 'bar';promiseget'foo'console.logvalue; // 'bar';
You can get to nested properties as well:
var promise = deferred foo: bar: 317 ;promiseconsole.logobjfoobar; // 317;promiseget'foo' 'bar'console.logvalue; // 317;
Schedule function call on returned object
var promise = deferred return arg*arg; ;promiseinvoke'foo' 3console.logresult; // 9;// For asynchronous functions use invokeAsyncvar promise = deferredsetTimeoutcallbacknull arg*arg;100;;promiseinvokeAsync'foo' 3console.logresult; // 9;
If promise expected value is a list that you want to spread into function arguments then use
var promise = deferred2 3;promisespreadconsole.loga + b; // 5;
In properly constructed flow, there should be no promises that are never resolved. If you want to be sure that it's not the case, or you suspect there are such issues, check whether deferred's monitor has something to say
By default monitor will log error for every promise that was not resolved in 5 seconds. You can customize that timeout, and handle errors with your own listener:
deferredmonitor10000// Called for each promise not resolved in 10 seconds time;
This extension affects performance and it's best not to use it in production environment
Being able to see how many promises were initialized (and where) in our flow can be helpful to track application issues, it's also good way to confirm that constructed flow works as intended.
deferredprofile; // Start collecting statistics//...var stats = deferredprofileEnd; // End profilingconsole.logstats.log; // See readable output
Example log output:
------------------------------------------------------------Deferred/Promise usage statistics:104540 Total promises initialized104540 Initialized as Unresolved0 Initialized as ResolvedUnresolved promises were initialized at:22590 at Object.module.exports.factory (/Users/medikoo/Projects/_packages/next/lib/fs/_memoize-watcher.js:21:10)11553 at Object.IsIgnored.init (/Users/medikoo/Projects/_packages/next/lib/fs/is-ignored.js:140:18)11553 at module.exports.factory (/Users/medikoo/Projects/_packages/next/lib/fs/_memoize-watcher.js:21:10)7854 at Object.Readdir.filterIgnored (/Users/medikoo/Projects/_packages/next/lib/fs/readdir.js:434:23)4619 at Object.module.exports.factory (/Users/medikoo/Projects/_packages/next/lib/fs/_memoize-watcher.js:21:10)3927 at Object.Readdir.filterByType (/Users/medikoo/Projects/_packages/next/lib/fs/readdir.js:222:15)3927 at Object.Readdir.filterByType (/Users/medikoo/Projects/_packages/next/lib/fs/readdir.js:236:15)3927 at Object.self (/Users/medikoo/Projects/_packages/next/lib/fs/readdir.js:164:12)3927 at Object.Readdir.readdir (/Users/medikoo/Projects/_packages/next/lib/fs/readdir.js:540:9)3927 at Object.self (/Users/medikoo/Projects/_packages/next/lib/fs/readdir.js:164:21)3729 at directory (/Users/medikoo/Projects/_packages/next/lib/fs/_watch-alt.js:95:2)2820 at Readdir.filterIgnored.promise.root (/Users/medikoo/Projects/_packages/next/lib/fs/readdir.js:517:9)2163 at basic (/Users/medikoo/Projects/_packages/next/lib/fs/is-gitrepo-root.js:14:8)2159 at buildMap (/Users/medikoo/Projects/_packages/next/lib/fs/is-ignored.js:117:22)2159 at Object.FindRoot (/Users/medikoo/Projects/_packages/next/lib/fs/find-root.js:18:15)1107 at Readdir.filterIgnored.promise.root (/Users/medikoo/Projects/_packages/next/lib/fs/readdir.js:527:11)697 at Object.Map.addPaths (/Users/medikoo/Projects/_packages/next/lib/fs/_get-conf-file-map.js:107:19)697 at readFile (/Users/medikoo/Projects/_packages/next/lib/fs/read-file.js:18:8)697 at Object.readRulesWatch (/Users/medikoo/Projects/_packages/next/lib/fs/_get-conf-file-map.js:45:12)247 at module.exports (/Users/medikoo/Projects/_packages/next/lib/fs/_watch.js:90:2)247 at module.exports (/Users/medikoo/Projects/_packages/next/lib/fs/_watch.js:90:13)1 at Object.Readdir.init (/Users/medikoo/Projects/_packa
Using profiler significantly affects performance don't use it in production environment.
Promises just by being rich objects introduce overhead over regular callbacks. If we do a lot asynchronous operations that are fast, performance of promise implementation that we rely on becomes a significant factor.
benchmark folder contains two tests. Tests reflect real use case I had in which performance of promise implementation appeared to be crucial.
Base of a test is lstat (fastest asynchronous call in node.js API), It's called 10000 times in parallel and sequentially.
Note for benchmark purists: This test does real I/O, but there's no way to produce shim which will provide more reliable results (shim based on
setImmediate adds more randomness to result than we got from calling real
Example output taken under Node v0.10.20 on 2008 MBP.
Promise overhead (calling one after another) x10000:1: 439ms Base (plain Node.js lstat call)2: 461ms Kew: Dedicated wrapper3: 609ms Bluebird: Dedicated wrapper4: 614ms Bluebird: Promisify (generic wrapper)5: 642ms Deferred: Dedicated wrapper6: 720ms Deferred: Promisify (generic wrapper)7: 792ms When: Dedicated wrapper8: 1068ms Q: Dedicated wrapper9: 1611ms Q: nbind (generic wrapper)Promise overhead (concurrent calls) x10000:1: 279ms Base (plain Node.js lstat call)2: 293ms Bluebird: Promisify (generic wrapper)3: 294ms Bluebird: Dedicated wrapper4: 329ms Kew: Dedicated wrapper5: 406ms When: Dedicated wrapper6: 430ms Deferred: Dedicated wrapper7: 598ms Deferred: Promisify (generic wrapper)8: 683ms Deferred: Map + Promisify9: 1610ms Q: Dedicated wrapper10: 3645ms Q: nbind (generic wrapper)
Covered by over 300 hundred unit tests
$ npm test