Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
forked fromkriskowal/q

A tool for making and composing asynchronous promises in JavaScript

License

NotificationsYou must be signed in to change notification settings

samarabbas/q

 
 

Repository files navigation

Build Status

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.

Getting Started

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 theqpackage
  • 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.

Tutorial

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.

Propagation

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 handlerbar 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 tobar
  • 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.

Chaining

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;});});}

Combination

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;}})})

Sequences

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));

Handling Errors

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})

The End

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.

The Beginning

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.

UsingQ.fcall

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);

Using Deferreds

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;}

The Middle

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(){})

Over the Wire

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")

Adapting Node

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.

Reference

A method-by-methodQ API reference is available on the wiki.

More Examples

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

Resources

License

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp