- Notifications
You must be signed in to change notification settings - Fork0
A tool for making and composing asynchronous promises in JavaScript
License
samarabbas/q
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
If a function cannot return a value or throw an exception withoutblocking, it can return a promise instead. A promise is an objectthat represents the return value or the thrown exception that thefunction may eventually provide. A promise can also be used as aproxy for aremote object to overcome latency.
On the first pass, promises can mitigate the “Pyramid ofDoom”: the situation where code marches to the right fasterthan it marches forward.
step1(function(value1){step2(value1,function(value2){step3(value2,function(value3){step4(value3,function(value4){// Do something with value4});});});});
With a promise library, you can flatten the pyramid.
Q.fcall(step1).then(step2).then(step3).then(step4).then(function(value4){// Do something with value4},function(error){// Handle any error from step1 through step4}).end();
With this approach, you also get implicit error propagation,just liketry
,catch
, andfinally
. An error instep1
will flow all the way tostep5
, where it’scaught and handled.
The callback approach is called an “inversion of control”.A function that accepts a callback instead of a return valueis saying, “Don’t call me, I’ll call you.”. Promisesun-invert the inversion, cleanly separating the inputarguments from control flow arguments. This simplifies theuse and creation of API’s, particularly variadic,rest and spread arguments.
The Q module can be loaded as:
- a
<script>
tag (creating aQ
global variable):~3.5 KB minified and gzipped. - a Node.js and CommonJS module available from NPM as the
q
package - a RequireJS module
Q can exchange promises with jQuery, Dojo, When.js, WinJS, and more.Additionally, there are many libraries that produce and consume Q promises foreverything from file system/database access or RPC to templating. For a list ofsome of the more popular ones, seeLibraries.
Please join the Q-Continuummailing list.
Promises have athen
method, which you can use to get the eventualreturn value (fulfillment) or thrown exception (rejection).
foo().then(function(value){},function(reason){})
Iffoo
returns a promise that gets fulfilled later with a returnvalue, the first function (the value handler) will be called with thevalue. However, if thefoo
function gets rejected later by athrown exception, the second function (the error handler) will becalled with the error.
Note that resolution of a promise is always asynchronous: that is, thevalue or error handler will always be called in the next turn of theevent loop (i.e.process.nextTick
in Node). This gives you a niceguarantee when mentally tracing the flow of your code, namely thatthen
will always return before either handler is executed.
Thethen
method returns a promise, which in this example, I’massigning tobar
.
varbar=foo().then(function(value){},function(reason){})
Thebar
variable becomes a new promise for the return value ofeither handler. Since a function can only either return a value orthrow an exception, only one handler will ever be called and it willbe responsible for resolvingbar
.
If you return a value in a handler,
bar
will get fulfilled.If you throw an exception in a handler
bar
will get rejected.If you return apromise in a handler,
bar
will “become”that promise. Being able to become a new promise is useful formanaging delays, combining results, or recovering from errors.
If thefoo()
promise gets rejected and you omit the error handler,theerror will go tobar
:
varbar=foo().then(function(value){})
If thefoo()
promise gets fulfilled and you omit the valuehandler, thevalue will go tobar
:
varbar=foo().then(null,function(error){})
Q promises provide afail
shorthand forthen
when you are onlyinterested in handling the error:
varbar=foo().fail(function(error){})
They also have afin
function that is like afinally
clause.The final handler gets called, with no arguments, when the promisereturned byfoo()
either returns a value or throws an error. Thevalue returned or error thrown byfoo()
passes directly tobar
.
varbar=foo().fin(function(){// close files, database connections, stop servers, conclude tests})
- If the handler returns a value, the value is ignored
- If the handler throws an error, the error passes to
bar
- If the handler returns a promise,
bar
gets postponed. Theeventual value or error has the same effect as an immediate returnvalue or thrown error: a value would be ignored, an error would beforwarded.
There are two ways to chain promises. You can chain promises eitherinside or outside handlers. The next two examples are equivalent.
returnfoo().then(function(fooValue){returnbar(fooValue).then(function(barValue){// if we get here without an error,// the value returned here// or the exception thrown here// resolves the promise returned// by the first line})})
returnfoo().then(function(fooValue){returnbar(fooValue);}).then(function(barValue){// if we get here without an error,// the value returned here// or the exception thrown here// resolves the promise returned// by the first line})
The only difference is nesting. It’s useful to nest handlers if youneed to capture bothfooValue
andbarValue
in the lasthandler.
functioneventualAdd(a,b){returna.then(function(a){returnb.then(function(b){returna+b;});});}
You can turn an array of promises into a promise for the whole,fulfilled array usingall
.
returnQ.all([eventualAdd(2,2),eventualAdd(10,20)])
If you have a promise for an array, you can usespread
as areplacement forthen
. Thespread
function “spreads” thevalues over the arguments of the value handler. The error handlerwill get called at the first sign of failure. That is, whichever ofthe recived promises fails first gets handled by the error handler.
functioneventualAdd(a,b){returnQ.spread([a,b],function(a,b){returna+b;})}
Butspread
callsall
initially, so you can skip it in chains.
returnfoo().then(function(info){return[info.name,FS.read(info.location,"utf-8")];// FS.read returns a promise, so this array// mixes values and promises}).spread(function(name,text){})
Theall
function returns a promise for an array of values. If oneof the given promise fails, the whole returned promise fails, notwaiting for the rest of the batch. If you want to wait for all of thepromises to either be fulfilled or rejected, you can useallResolved
.
Q.allResolved(promises).then(function(promises){promises.forEach(function(promise){if(promise.isFulfilled()){varvalue=promise.valueOf();}else{varexception=promise.valueOf().exception;}})})
If you have a number of promise-producing functions that needto be run sequentially, you can of course do so manually:
returnfoo(initialVal).then(bar).then(baz).then(quux);
However, if you want to run a dynamically constructed sequence offunctions, you'll want something like this:
varfuncs=[foo,bar,baz,quux];varresult=Q.resolve(initialVal);funcs.forEach(function(f){result=result.then(f);});returnresult;
You can make this slightly more compact usingreduce
:
returnfuncs.reduce(function(soFar,f){returnsoFar.then(f);},Q.resolve(initialVal));
One sometimes-unintuive aspect of promises is that if you throw anexception in the value handler, it will not be be caught by the errorhandler.
foo().then(function(value){thrownewError("Can't bar.");},function(error){// We only get here if "foo" fails})
To see why this is, consider the parallel between promises andtry
/catch
. We aretry
-ing to executefoo()
: the errorhandler represents acatch
forfoo()
, while the value handlerrepresents code that happensafter thetry
/catch
block.That code then needs its owntry
/catch
block.
In terms of promises, this means chaining your error handler:
foo().then(function(value){thrownewError("Can't bar.");}).fail(function(error){// We get here with either foo's error or bar's error})
When you get to the end of a chain of promises, you should eitherreturn the last promise or end the chain. Since handlers catcherrors, it’s an unfortunate pattern that the exceptions can gounobserved.
So, either return it,
returnfoo().then(function(){return"bar";})
Or, end it.
foo().then(function(){return"bar";}).end()
Ending a promise chain makes sure that, if an error doesn’t gethandled before the end, it will get rethrown and reported.
This is a stopgap. We are exploring ways to make unhandled errorsvisible without any explicit handling.
Everything above assumes you get a promise from somewhere else. Thisis the common case. Every once in a while, you will need to create apromise from scratch.
You can create a promise from a value usingQ.fcall
. This returns apromise for 10.
returnQ.fcall(function(){return10;});
You can also usefcall
to get a promise for an exception.
returnQ.fcall(function(){thrownewError("Can't do it");})
As the name implies,fcall
can call functions, or even promisedfunctions. This uses theeventualAdd
function above to add twonumbers.
returnQ.fcall(eventualAdd,2,2);
If you have to interface with asynchronous functions that are callback-basedinstead of promise-based, Q provides a few shortcuts (likeQ.ncall
andfriends). But much of the time, the solution will be to usedeferreds.
vardeferred=Q.defer();FS.readFile("foo.txt","utf-8",function(error,text){if(error){deferred.reject(newError(error));}else{deferred.resolve(text);}});returndeferred.promise;
Note that a deferred can be resolved with a value or a promise. Thereject
function is a shorthand for resolving with a rejectedpromise.
// this:deferred.reject(newError("Can't do it"));// is shorthand for:varrejection=Q.fcall(function(){thrownewError("Can't do it");});deferred.resolve(rejection);
This is a simplified implementation ofQ.delay
.
functiondelay(ms){vardeferred=Q.defer();setTimeout(deferred.resolve,ms);returndeferred.promise;}
This is a simplified implementation ofQ.timeout
functiontimeout(promise,ms){vardeferred=Q.defer();Q.when(promise,deferred.resolve);Q.when(delay(ms),function(){deferred.reject(newError("Timed out"));});returndeferred.promise;}
If you are using a function that may return a promise, but just mightreturn a value if it doesn’t need to defer, you can use the “static”methods of the Q library.
Thewhen
function is the static equivalent forthen
.
returnQ.when(valueOrPromise,function(value){},function(error){});
All of the other methods on a promise have static analogs with thesame name.
The following are equivalent:
returnQ.all([a,b]);
returnQ.fcall(function(){return[a,b];}).all();
When working with promises provided by other libraries, you shouldconvert it to a Q promise. Not all promise libraries make the sameguarantees as Q and certainly don’t provide all of the same methods.Most libraries only provide a partially functionalthen
method.This thankfully is all we need to turn them into vibrant Q promises.
returnQ.when($.ajax(...)).then(function(){})
If there is any chance that the promise you receive is not a Q promiseas provided by your library, you should wrap it using a Q function.You can even useQ.invoke
as a shorthand.
returnQ.invoke($,'ajax', ...).then(function(){})
A promise can serve as a proxy for another object, even a remoteobject. There are methods that allow you to optimistically manipulateproperties or call functions. All of these interactions returnpromises, so they can be chained.
direct manipulation using a promise as a proxy-------------------------- -------------------------------value.foo promise.get("foo")value.foo = value promise.put("foo", value)delete value.foo promise.del("foo")value.foo(...args) promise.post("foo", [args])value.foo(...args) promise.invoke("foo", ...args)value(...args) promise.fapply([args])value(...args) promise.fcall(...args)
If the promise is a proxy for a remote object, you can shaveround-trips by using these functions instead ofthen
. To takeadvantage of promises for remote objects, check outQ-Comm.
Even in the case of non-remote objects, these methods can be used asshorthand for particularly-simple value handlers. For example, youcan replace
returnQ.fcall(function(){return[{foo:"bar"},{foo:"baz"}];}).then(function(value){returnvalue[0].foo;})
with
returnQ.fcall(function(){return[{foo:"bar"},{foo:"baz"}];}).get(0).get("foo")
There is amakeNodeResolver
method on deferreds that is handy forthe NodeJS callback pattern.
vardeferred=Q.defer();FS.readFile("foo.txt","utf-8",deferred.makeNodeResolver());returndeferred.promise;
And there areQ.ncall
andQ.ninvoke
for even shorterexpression.
returnQ.ncall(FS.readFile,FS,"foo.txt","utf-8");
returnQ.ninvoke(FS,'readFile',"foo.txt","utf-8");
There is also aQ.nbind
function that that creates a reusablewrapper.
varreadFile=Q.nbind(FS.readFile,FS)returnreadFile("foo.txt","utf-8");
Note that, since promises are always resolved in the next turn of theevent loop, working with streamscan be tricky. Theessential problem is that, since Node does not buffer input, it isnecessary to attach your"data"
event listeners immediately,before this next turn comes around. There are a variety of solutionsto this problem, and even some hope that in future versions of Node itwillbe ameliorated.
A method-by-methodQ API reference is available on the wiki.
A growingexamples gallery is available on the wiki, showing how Qcan be used to make everything better. From XHR to database access to accessingthe Flickr API, Q is there for you.
Copyright 2009-2012 Kristopher Michael KowalMIT License (enclosed)
About
A tool for making and composing asynchronous promises in JavaScript