GitHub - cscott/prfun: Helper functions for ES6 promises (original) (raw)
prfun
Helper functions for ES6 Promises.
The Promise
implementation included in the ES6 "harmony" Javascript specification is missing some of the useful utility functions that are common in popular packages like bluebird, when, and q.
This package supplies them. It optionally loads a Promise
implementation from core-js, if there is not already a Promise
implementation present. The prfun
package also requires an implementation of Object.setPrototypeOf
; it will attempt to load this from core-js if not already present. Implementations loaded from core-js do not pollute the global namespace.
Portions of the API and test suite are borrowed from bluebird, when, and q.
Usage
Unlike many other utility packages for Promise
, prfun
does not pollute the global namespace. By default prfun
creates a Promise
subclass, using ES6 semantics. This means you use it like:
var Promise = require('prfun'); // subclasses global.Promise
// note that global.Promise !== Promise after this point
or
var SomeOtherPromise = require( /*something*/ );
var Promise = require('prfun/wrap')( SomeOtherPromise );
// Note that the same `Promise` object will always be
// returned if given the same `SomeOtherPromise` to wrap.
Note that the SomeOtherPromise
implementation must support Promise
subclassing using ES6 semantics. (The implementations in babybird,es6-shim, and core-js are known to do so.) We will call the subclass created by prfun
a "prfun
Promise
".
According to the ES6 Promise
spec, all Promise
methods (including the new ones added by prfun
) will return an instance of the subclass when invoked on an instance of the subclass. That is, if you are given a prfun
Promise
and you call then
on it, the result will be another prfun
Promise
. So within your own code you can assume that all prfun
helper methods will be present, and they will all return prfun
Promise
s which also contain all the prfun
helper methods.
If your code is given a promise from an outside API, and you can't guarantee that it is a prfun
Promise, then you can usePromise.resolve
in order to cast the outside promise to a prfun
Promise
. For example:
var Promise = require('prfun'); // this is a "prfun Promise"
function myApi(externalPromise) {
return Promise.resolve(externalPromise).tap(function(value) {
// we can call 'tap' after resolving the external promise
}); // this result will also be a "prfun Promise"
}
In order to modify the global Promise
object (instead of subclassing), use:
require('prfun/smash');
// global.Promise.reduce, global.Promise.tap, etc, now exist.
This is how prfun
worked by default prior to version 2.0, but it it not recommended: stomping on global objects is never a good idea, and future changes to the Promise
object in ES7 or incompatible methods added by your third-party Promise
implementation or other libraries could break your code in mysterious ways.
API
Collections
Methods of Promise
instances and core static methods of the Promise class to deal with collections of promises or mixed promises and values.
Promise.all(Array<dynamic>|Iterable values)
→ Promise
This is anES6 built-in. Note that, unlike many libraries, the ES6 method accepts as its argument only an array (or iterable), not a promise for an array (or iterable). Also, the ES6 method does not preserve sparsity in the passed array.
Given an array or iterable which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are fulfilled. The promise's fulfillment value is an array with fulfillment values at respective positions to the original iterable. If any promise in the iterable rejects, the returned promise is rejected with the rejection reason.
Promise#all()
→ Promise
Convenience method for:
promise.then(function(value) { return Promise.all(value); });
See Promise.all.
Promise.filter(Array<dynamic>|Promise values, Function callback [, Object thisArg])
→ Promise
Filters an array-like, or a promise of an array-like, using the providedcallback
function.
Convenience method for:
Promise.resolve(values).filter(callback, thisArg);
See Promise#filter.
Promise#filter(Function callback [, Object thisArg])
→ Promise
Call the given callback
function once for each element in (a promise of) an array which contains a promises (or a mix of promises and values), and construct a new array of all the values for which the callback returns (a promise of) a true value. The callback
function has the signature (item, index, array)
where item
is the resolved value of the promise in the input array at index
. If any promise in the input array is rejected the returned promise is rejected as well.
If a thisArg
parameter is provided, it will be passed to callback
when invoked, for use as its this
value. Otherwise, the valueundefined
will be passed for use as its this
value.
Note that the callback is invoked on each element in the array as soon as possible; that is, as soon as the promise for each element is resolved the callback is invoked for that element, without waiting for other elements to be resolved.
The behavior of filter
matchesArray.prototype.filteras much as possible. Note that this means that non-array objects are often accepted without error, and any object without a length field returns a zero-length array. For example,Array.prototype.filter.call(123, Object.toString)
returns []
.Promise#filter
rejects with a TypeError
if Array.prototype.filter
would throw a TypeError
.
The original array is not modified.
Promise.join([dynamic value...])
→ Promise
Like Promise.all but instead of having to pass an array, the array is generated from the passed variadic arguments.
So instead of:
Promise.all([a, b]).spread(function(aResult, bResult) {
});
You can do:
Promise.join(a, b).spread(function(aResult, bResult) {
});
Promise.map(Array<dynamic>|Promise values, Function mapper [, Object thisArg])
→ Promise
Maps an array-like, or a promise of an array-like, using the providedmapper
function.
Convenience method for:
Promise.resolve(values).map(mapper, thisArg);
See Promise#map.
Promise#map(Function mapper [, Object thisArg])
→ Promise
Map (a promise of) an array which contains a promises (or a mix of promises and values) with the given mapper
function with the signature (item, index, array)
where item
is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well.
If the mapper
function returns promises or thenables, the returned promise will wait for all the mapped results to be resolved as well, as if Promise.all were invoked on the result.
If a thisArg
parameter is provided, it will be passed to mapper
when invoked, for use as its this
value. Otherwise, the valueundefined
will be passed for use as its this
value.
Note that the mapper
function is invoked on each element in the array as soon as possible; that is, as soon as the promise for each element is resolved mapper
is invoked for that element, without waiting for other elements to be resolved.
The behavior of map
matchesArray.prototype.mapas much as possible. Note that this means that non-array objects are often accepted without error, and any object without a length field returns a zero-length array. For example,Array.prototype.map.call(123, Object.toString)
returns []
.Promise#map
rejects with a TypeError
if Array.prototype.map
would throw a TypeError
.
The original array is not modified.
Promise.props(Object|Promise object)
→ Promise
Like Promise.all but for object properties instead of array items. Returns a promise that is fulfilled when all the properties of the object are fulfilled. The promise's fulfillment value is an object with fulfillment values at respective keys to the original object. If any promise in the object rejects, the returned promise is rejected with the rejection reason.
If object
is a Promise
or "thenable" --- that is, if it has a property named then
which is a callable function --- then it will be treated as a promise for the object, rather than for its properties. All other objects are treated for their own enumerable properties, as returned byObject.keys.
Promise.props({ pictures: getPictures(), comments: getComments(), tweets: getTweets() }).then(function(result){ console.log(result.tweets, result.pictures, result.comments); });
Note that if you have no use for the result object other than retrieving the properties, it is more convenient to usePromise.all and Promise#spread:
Promise.all([getPictures(), getComments(), getTweets()]) .spread(function(pictures, comments, tweets) { console.log(pictures, comments, tweets); });
The original object is not modified.
Promise#props()
→ Promise
Convenience method for:
promise.then(function(value) { return Promise.props(value); });
See Promise.props.
Promise.race(Array<dynamic>|Iterable values)
→ Promise
This is anES6 built-in. Note that, unlike many libraries, the ES6 method accepts as its argument only an array (or iterable), not a promise for an array (or iterable). The ES6 method also returnsa promise that never resolves if you promise a zero-length array; user beware!
Given an array or iterable which contains promises (or a mix of promises and values) return a promise that is fulfilled or rejected as soon as a promise in the array is fulfilled or rejected with the respective rejection reason or fulfillment value.
Promise#race()
→ Promise
Convenience method for:
promise.then(function(value) { return Promise.race(value); });
See Promise.race.
Promise.reduce(Array<dynamic>|Promise values, Function reducer [, dynamic initialValue])
→ Promise
Reduce an array-like, or a promise of an array-like, left-to-right using the provided reducer
function.
Convenience method for:
Promise.resolve(values).reduce(reducer /, initialValue/);
See Promise#reduce.
Promise#reduce(Function reducer [, dynamic initialValue])
→ Promise
Reduce an array, or a promise of an array, which contains a promises (or a mix of promises and values) left-to-right with the givenreducer
function with the signature (previousValue, currentValue, index, array)
where currentValue
is the resolved value of a respective promise in the input array, and previousValue
is the value returned by the previous invocation of the reducer
. If thereducer
returns a promise or thenable, it will be resolved andpreviousValue
will be the resolved value. The initialValue
may also be a promise or thenable. If any promise is rejected (in the input array, an initialValue
, or a promise returned by resolved
), returned promise is rejected as well.
Promises are resolved in order: first the initialValue (if any), then the first item in the array, then the returned value from theresolver
(if it is a promise), then the next item in the array, then the next returned value from resolver
, etc.
For example: Read given files sequentially while summing their contents as an integer. Each file contains just the text 10
.
var readFileAsync = Promise.promisify(fs.readFile, false, fs); Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) { return readFileAsync(fileName, "utf8").then(function(contents) { return total + parseInt(contents, 10); }); }, 0).then(function(total) { //Total is 30 });
The behavior of reduce
matchesArray.prototype.reduceas much as possible. Note that this means that non-array objects are often accepted without error, and any object without a length field is treated as a zero-length array. For example,Array.prototype.reduce.call(123, Math.pow, 42)
returns 42
.Promise#reduce
rejects with a TypeError
if Array.prototype.reduce
would throw a TypeError
, for example if you pass a zero-length array without an initialValue
.
The original array is not modified.
Promise.reduceRight(Array<dynamic>|Promise values, Function reducer [, dynamic initialValue])
→ Promise
Reduce an array-like, or a promise of an array-like, right-to-left using the provided reducer
function.
Convenience method for:
Promise.resolve(values).reduceRight(reducer /, initialValue/);
See Promise#reduceRight.
Promise#reduceRight(Function reducer [, dynamic initialValue])
→ Promise
Reduce an array, or a promise of an array, which contains a promises (or a mix of promises and values) right-to-left with the givenreducer
function with the signature (previousValue, currentValue, index, array)
where currentValue
is the resolved value of a respective promise in the input array, and previousValue
is the value returned by the previous invocation of the reducer
. If thereducer
returns a promise or thenable, it will be resolved andpreviousValue
will be the resolved value. The initialValue
may also be a promise or thenable. If any promise is rejected (in the input array, an initialValue
, or a promise returned by resolved
), returned promise is rejected as well.
Promises are resolved in order: first the initialValue (if any), then the last item in the array, then the returned value from theresolver
(if it is a promise), then the next-to-last item in the array, then the next returned value from resolver
, etc.
The behavior of reduceRight
matchesArray.prototype.reduceRightas much as possible. Note that this means that non-array objects are often accepted without error, and any object without a length field is treated as a zero-length array. For example,Array.prototype.reduceRight.call(123, Math.pow, 42)
returns 42
.Promise#reduceRight
rejects with a TypeError
if Array.prototype.reduceRight
would throw a TypeError
, for example if you pass a zero-length array without an initialValue
.
The original array is not modified.
Promise#spread([Function fulfilledHandler] [, Function rejectedHandler ])
→ Promise
Like calling Promise#then
, but the fulfillment value or rejection reason is assumed to be an array, which is flattened to the formal parameters of the handlers.
Promise.all([task1, task2, task3]).spread(function(result1, result2, result3){
});
Normally when using Promise#then
the code would be like:
Promise.all([task1, task2, task3]).then(function(results){ var result1 = results[0]; var result2 = results[1]; var result3 = results[2]; });
This is useful when the results
array contains items that are not conceptually items of the same list.
Utility
Shorthands for common operations.
Promise.bind(dynamic thisArg)
-> Promise
Sugar for Promise.resolve(undefined).bind(thisArg);
. See Promise#bind.
Promise#bind(dynamic thisArg)
-> Promise
Create a promise that follows this promise, but is bound to the giventhisArg
value. A bound promise will call its handlers with this
set to the bound value. Additionally promises derived from a bound promise will also be bound promises with the same thisArg
binding as the original promise.
Without arrow functions that provide lexical this
, the correspondence between async and sync code breaks down when writing object-oriented code. The Promise#bind
method alleviates this.
Consider:
MyClass.prototype.method = function() { try { var contents = fs.readFileSync(this.file); var url = urlParse(contents); var result = this.httpGetSync(url); var refined = this.refine(result); return this.writeRefinedSync(refined); } catch (e) { this.error(e.stack); } };
The above has a direct translation:
MyClass.prototype.method = function() { return fs.readFileAsync(this.file).bind(this) .then(function(contents) { var url = urlParse(contents); return this.httpGetAsync(url); }).then(function(result){ var refined = this.refine(result); return this.writeRefinedAsync(refined); }).catch(function(e){ this.error(e.stack); }); };
Promise#bind
also has a useful side purpose --- promise handlers don't need to share a function to use shared state:
somethingAsync().bind({}) .then(function (aValue, bValue) { this.aValue = aValue; this.bValue = bValue; return somethingElseAsync(aValue, bValue); }).then(function (cValue) { return this.aValue + this.bValue + cValue; });
The above without Promise#bind
could be achieved with:
var scope = {}; somethingAsync() .then(function (aValue, bValue) { scope.aValue = aValue; scope.bValue = bValue; return somethingElseAsync(aValue, bValue); }).then(function (cValue) { return scope.aValue + scope.bValue + cValue; });
However, there are many differences when you look closer:
- Requires a statement so cannot be used in an expression context.
- If not there already, an additional wrapper function is required to avoid leaking or sharing
scope
. - The handler functions are now closures, thus less efficient and not reusable.
Note that bind is only propagated with promise transformation. If you create new promise chains inside a handler, those chains are not bound to the "outer" this
:
something().bind(var1).then(function(){
//this
is var1 here
return Promise.all(getStuff()).then(function(results){
//this
is undefined here
//refine results here etc
});
}).then(function(){
//this
is var1 here
});
If you don't want to return a bound promise to the consumers of a promise, you can rebind the chain at the end:
MyClass.prototype.method = function() { return fs.readFileAsync(this.file).bind(this) .then(function(contents) { var url = urlParse(contents); return this.httpGetAsync(url); }).then(function(result){ var refined = this.refine(result); return this.writeRefinedAsync(refined); }).catch(function(e){ this.error(e.stack); }).bind(); // Unbind the promise. };
Rebinding can also be abused to do something gratuitous like this:
Promise.resolve("my-element") .bind(document) .then(document.getElementById) .bind(console) .then(console.log);
The above does console.log(document.getElementById("my-element"));
.
Promise#call(String propertyName [, Promise|dynamic arg...])
→ Promise
This is a convenience method for doing:
promise.then(function(obj){ return obj[propertyName].call(obj, arg...); });
If any of the arg...
are Promises, they will be resolved before the method is invoked.
Promise#get(String propertyName)
→ Promise
This is a convenience method for doing:
promise.then(function(obj){ return obj[propertyName]; });
Promise#return(Promise|dynamic value)
→ Promise
Convenience method for:
promise.then(function() { return value; });
in the case where value
doesn't change its value.
That means value
is bound at the time of calling Promise#return
so this will not work as expected:
function getData() { var data;
return query().then(function(result) {
data = result;
}).return(data);
}
because data
is undefined
at the time .return
is called.
Promise#tap(Function handler)
→ Promise
Convenience method for:
promise.then(function(value) { return Promise.resolve(handler(value)).return(value); });
That is, it waits for the promise, then invokes the handler with the promised value. It waits for any promise returned by the handler, then returns the original value.
Common use case is to add logging to an existing promise chain:
doSomething() .then(...) .then(...) .tap(console.log) .then(...) .then(...)
Promise#then0([Function onFulfilled [, Function onRejected]])
→ undefined
This is identical to Promise#then
except that it does not return a value. Some Promise
implementations (for example, babybird) export a then0
implementation which is substantially faster than calling Promise#then
. If such an implementation is present, its implementation will be used. Otherwise prfun
will provide a shim implementation that just calls Promise#then
and discards the result.
This allows you to use then0
freely in your own code whenever you don't care about the result, and prfun
will use the most efficient implementation available.
Promise#throw(Promise|dynamic reason)
→ Promise
Convenience method for:
promise.then(function() { throw reason; });
...except that reason
is first resolved, if it is a Promise
or thenable.
Same limitations apply as with Promise#return.
Promise.defer()
→ PromiseResolver
Create a promise with undecided fate and return a PromiseResolver
to control it.
The use of Promise.defer
is discouraged---it is much more awkward and error-prone than using new Promise
. It is provided only for compatibility with older libraries like q, when, etc.
A PromiseResolver
contains three fields: promise
, resolve
, andreject
. It also contains two additional helpers, which are implemented as getters: resolver
and callback
. The resolver
property returns an object with only resolve
and reject
fields, as in the when package. The callback
property returns a node-style callback function with signature (err, result)
which will invokereject
and resolve
as appropriate. This is like the callback
property in bluebird or the result of makeNodeResolver
in q.
Promise#done()
→ undefined
Terminate a chain of promises, ensuring that any unhandled rejections are rethrown so as to trigger the top-level unhandled exception handler (which will typically result in a message on console).
The use of Promise#done
is discouraged---it is hoped that future promise implementations will provide special development tools to track orphaned promises. This method is provided for compatibility with older libraries, and as a make-do until better debugging tools are integrated into JavaScript engines.
Try/caught/finally
Promise.try(Function fn [, dynamic ctx [, dynamic args...]] )
→ Promise
Start the chain of promises with Promise.try
. Any synchronous exceptions will be turned into rejections on the returned promise.
function getUserById(id) { return Promise.try(function(){ if (typeof id !== "number") { throw new Error("id must be a number"); } return db.getUserById(id); }); }
Now if someone uses this function, they will catch all errors in their Promise .catch
handlers instead of having to handle both synchronous and asynchronous exception flows.
If provided, ctx
becomes the this
value for the function call. If it is a promise, it is first resolved. Any args
provided are resolved (if they are promises) and passed as arguments to the function call.
Promise#caught([Function ErrorClass|Function predicate...], Function handler)
→ Promise
This extends .catch
to work more like catch-clauses in languages like Java or C#. Instead of manually checking instanceof
or.name === "SomeError"
, you may specify a number of error constructors which are eligible for this catch handler. The catch handler that is first met that has eligible constructors specified, is the one that will be called.
Example:
somePromise.then(function(){ return a.b.c.d(); }).caught(TypeError, function(e){ //If a is defined, will end up here because //it is a type error to reference property of undefined }).caught(ReferenceError, function(e){ //Will end up here if a wasn't defined at all }).caught(function(e){ //Generic catch-the rest, error wasn't TypeError nor //ReferenceError });
You may also add multiple filters for a catch handler:
somePromise.then(function(){ return a.b.c.d(); }).caught(TypeError, ReferenceError, function(e){ //Will end up here on programmer error }).caught(NetworkError, TimeoutError, function(e){ //Will end up here on expected everyday network errors }).catch(function(e){ //Catch any unexpected errors });
For a parameter to be considered a type of error that you want to filter, you need the constructor to have its .prototype
property beinstanceof Error
.
Such a constructor can be minimally created like so:
function MyCustomError() {} MyCustomError.prototype = Object.create(Error.prototype);
Using it:
Promise.resolve().then(function(){ throw new MyCustomError(); }).caught(MyCustomError, function(e){ //will end up here now });
However, you can obtain better stack traces and string output with:
function MyCustomError(message) { this.message = message; this.name = "MyCustomError"; if (Error.captureStackTrace) // v8 environments Error.captureStackTrace(this, MyCustomError); } MyCustomError.prototype = Object.create(Error.prototype); MyCustomError.prototype.constructor = MyCustomError;
Using CoffeeScript's class
for the same:
class MyCustomError extends Error constructor: (@message) -> @name = "MyCustomError" Error.captureStackTrace?(this, MyCustomError)
This method also supports predicate-based filters. If you pass a predicate function instead of an error constructor, the predicate will receive the error as an argument. The return result of the predicate will be used determine whether the error handler should be called.
Predicates should allow for very fine grained control over caught errors: pattern matching, error-type sets with set operations and many other techniques can be implemented on top of them.
Example of using a predicate-based filter:
var request = Promise.promisify(require("request"), ['response', 'body']);
function clientError(e) { return e.code >= 400 && e.code < 500; }
request("http://www.google.com").then(function(result) { console.log(result.body); }).caught(clientError, function(e){ //A client error like 400 Bad Request happened });
Promise#finally(Function handler)
→ Promise
Pass a handler that will be called regardless of this promise's fate. Returns a new promise chained from this promise, which will become resolved with the same fulfillment value or rejection reason as this promise. However, if handler
returns a promise, the resolution of the returned promise will be delayed until the promise returned from handler
is finished. If handler
throws an exception or returns a rejected promise, the returned promise will reject in the same way. (This matches the JavaScript semantics for exceptions thrown inside finally
clauses.)
Consider the example:
function anyway() { $("#ajax-loader-animation").hide(); }
function ajaxGetAsync(url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest; xhr.addEventListener("error", reject); xhr.addEventListener("load", resolve); xhr.open("GET", url); xhr.send(null); }).then(anyway, anyway); }
This example doesn't work as intended because the then
handler actually swallows the exception and returns undefined
for any further chainers.
The situation can be fixed with Promise#finally
:
function ajaxGetAsync(url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest; xhr.addEventListener("error", reject); xhr.addEventListener("load", resolve); xhr.open("GET", url); xhr.send(null); }).finally(function(){ $("#ajax-loader-animation").hide(); }); }
Now the animation is hidden but an exception or the actual return value will automatically skip the finally and propagate to further chainers. This is more in line with the synchronous finally
keyword.
Promise#finally
works like Q's finally method, unless callback
returns a rejected promise.
Note that the parallel with synchronous finally
is not exact:
// as expected: (function() { try { return 1; } finally { throw "2"; } })(); // throws "2" Promise.resolve(1).finally(function() { throw "2"; }); // rejects with "2"
// but: (function() { try { return 1; } finally { return 2; } })(); // returns 2 Promise.resolve(1).finally(function() { return 2; }); // resolves to '1'
// compare: (function() { try { return 1; } finally { 2; } })(); // returns 1
This asymmetry is because the Promise
API can't distinguish the return
statement from an expression evaluating to a value.
Method wrappers and helpers
Functions for writing promise-returning methods.
Promise.guard(Function|Number condition, Function fn)
→ Function
Limit the concurrency of a function fn
. Creates a new function whose concurrency is limited by condition
. This can be useful with operations such as Promise.map, Promise.all, etc that allow tasks to execute in "parallel", to limit the number which can be in-flight simultanously.
The condition
argument is a concurrency limiting condition, such asPromise.guard.n. If condition
is a number, it will be treated as if it were Promise.guard.n(condition)
.
Example:
// Using Promise.guard with Promise.map to limit concurrency // of the mapFunc
var guardedAsyncOperation, mapped;
// Allow only 1 inflight execution of guarded guardedAsyncOperation = Promise.guard(1, asyncOperation);
mapped = Promise.map(array, guardedAsyncOperation); mapped.then(function(results) { // Handle results as usual });
Example:
// Using Promise.guard with Promise.all to limit concurrency // across all tasks
var guardTask, tasks, taskResults;
tasks = [/* Array of async functions to execute as tasks */];
// Use bind() to create a guard that can be applied to any function
// Only 2 tasks may execute simultaneously.
// Note that all guarded tasks share the same condition instance
// (Promise.guard.n(2)
) -- if we had passed 2
instead they
// would each have their own guard, which wouldn't do what we want.
guardTask = Promise.guard.bind(Promise, Promise.guard.n(2));
// Use guardTask to guard all the tasks. tasks = tasks.map(guardTask);
// Execute the tasks with concurrency/"parallelism" limited to 2 taskResults = Promise.all(tasks); taskResults.then(function(results) { // Handle results as usual });
Promise.guard.n(Number number)
→ Function
Creates a condition that allows at most number
of simultaneous executions inflight.
var condition = Promise.guard.n(number);
Promise.method(Function fn)
→ Function
Returns a new function that wraps the given function fn
. The new function will always return a promise that is fulfilled with the original function's return value or rejected with thrown exceptions from the original function. It will also unwrap any arguments (including this
) which are promises, converting them to their fulfilled values.
This method is convenient when a function can sometimes return synchronously or throw synchronously.
Example without using Promise.method
:
MyClass.prototype.method = function(input) { if (!this.isValid(input)) { return Promise.reject(new TypeError("input is not valid")); }
if (this.cache(input)) {
return Promise.resolve(this.someCachedValue);
}
return db.queryAsync(input).bind(this).then(function(value) {
this.someCachedValue = value;
return value;
});
};
Using Promise.method
, there is no need to manually wrap direct return or throw values into a promise:
MyClass.prototype.method = Promise.method(function(input) { if (!this.isValid(input)) { throw new TypeError("input is not valid"); }
if (this.cachedFor(input)) {
return this.someCachedValue;
}
return db.queryAsync(input).bind(this).then(function(value) {
this.someCachedValue = value;
return value;
});
});
See also Q.promised,when.lift.
Promise#nodify([Function callback])
→ Promise
Register a node-style callback on this promise. When this promise is is either fulfilled or rejected, the node callback will be called back with the node.js convention, where error reason is the first argument and success value is the sec ond argument. The error argument will benull
in case of success.
Returns back this promise instead of creating a new one. If thecallback
argument is not a function, this method does not do anything.
This can be used to create APIs that both accept node-style callbacks and return promises:
function getDataFor(input, callback) { return dataFromDataBase(input).nodify(callback); }
The above function can then make everyone happy.
Promises:
getDataFor("me").then(function(dataForMe) { console.log(dataForMe); });
Normal callbacks:
getDataFor("me", function(err, dataForMe) { if( err ) { console.error( err ); } else { console.log(dataForMe); } });
Promise.promisify(Function nodeFunction [, dynamic pattern [, dynamic receiver]])
→ Function
Returns a function that will wrap the given nodeFunction
. Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given node function. The node function should conform to node.js convention of accepting a callback as last argument and calling that callback with error as the first argument and success value(s) in the second and following arguments.
If the pattern
is true
, the fulfillment value will be an array containing the callback arguments.
If the pattern
is not present or falsy, the fulfillment value will be the second value passed to the callback. (This is useful in the common case where only a single value is provided to the callback.)
If the pattern
is an array of names, the fulfillment value will be an object with the callback arguments assigned to named fields in the order given by pattern
.
If you pass a receiver
, the nodeFunction
will be called as a method on the receiver
(that is, this
will be set to receiver
whennodeFunction
is invoked).
Example of promisifying the asynchronous readFile
of node.js fs
-module:
var fs = require('fs'); var readFile = Promise.promisify(fs.readFile, false, fs);
readFile("myfile.js", "utf8").then(function(contents){ return eval(contents); }).then(function(result){ console.log("The result of evaluating myfile.js", result); }).caught(SyntaxError, function(e){ console.log("File had syntax error", e); //Catch any other error }).catch(function(e){ console.log("Error reading file", e); });
Tip
Use Promise#spread with APIs that have multiple success values:
var request = Promise.promisify(require('request'), true); request("http://www.google.com").spread(function(response, body) { console.log(body); }).catch(function(err) { console.error(err); });
The above uses the requestlibrary which has a callback signature of multiple success values.
Since prfun
version 1.0.0.
Timers
Methods to delay and time out promises.
Promise.delay([dynamic value,] int ms)
→ Promise
Returns a promise that will be fulfilled with value
(or undefined
) after given ms
milliseconds. If value
is a promise, the delay will start counting down when it is fulfilled and the returned promise will be fulfilled with the fulfillment value of the value
promise.
Promise.delay(500).then(function(){ console.log("500 ms passed"); return "Hello world"; }).delay(500).then(function(helloWorldString) { console.log(helloWorldString); console.log("another 500 ms passed") ; });
Promise#delay(int ms)
→ Promise
Convenience method for:
See Promise.delay.
Promise#timeout(int ms [, String message])
→ Promise
Returns a promise that will be fulfilled with this promise's fulfillment value or rejection reason. However, if this promise is not fulfilled or rejected within ms
milliseconds, the returned promise is rejected with a Promise.TimeoutError
instance.
You may specify a custom error message with the message
parameter.
The example function fetchContent
tries to fetch the contents of a web page with a 50ms timeout and sleeping 100ms between each retry. If there is no response after 5 retries, then the returned promise is rejected with a ServerError
(made up error type).
function fetchContent(retries) { if (!retries) retries = 0; var jqXHR = $.get("http://www.slowpage.com"); //Cast the jQuery promise into a bluebird promise return Promise.resolve(jqXHR) .timeout(50) .caught(Promise.TimeoutError, function() { if (retries < 5) { return Promise.delay(100).then(function(){ return fetchContent(retries+1); }); } else { throw new ServerError("not responding after 5 retries"); } }); }
Generators
Using ECMAScript6 generators feature to implement better syntax for promises.
Note: Requires an environment that supports ES6 generators and the yield
keyword. Node >= 0.11.2
with the --harmony-generators
command-line flag will work, or Node >= 4
with no special flags.
Promise.async(GeneratorFunction generatorFunction [, int cbArg])
→ Function
Takes a function that can use yield
to await the resolution of promises while control is transferred back to the JS event loop. You can write code that looks and acts like synchronous code, even using synchronous try
, catch
and finally
. Returns a function which returns a Promise
.
If the optional cbArg
is present, then Promise.nodify
is invoked on the result with the given (optional) argument as a parameter.
// Use Promise.async to create a function that returns a Promise var getRecentTodosForUser = Promise.async(function*(todosFilter, userId) { var todos = yield getTodosForUser(userId); return todos.filter(todosFilter); });
function getTodosForUser(userId) { // returns a promise for an array of the user's todos }
// Get (a promise for) the todos for user 123, and filter them
// using the isRecentTodo
filter.
var filteredTodos = getRecentTodosForUser(isRecentTodo, 123);
filteredTodos.then(showTodos, showError);
In addition to try
, catch
, and finally
, return
also works as expected. In this revised example, yield
allows us to return a result and move error handling out to the caller.
// Use Promise.async to create a function that returns a Promise var getRecentTodosForUser = Promise.async(function*(todosFilter, userId) { var todos; try { todos = yield getTodosForUser(userId); showTodos(todos.filter(todosFilter)); } catch(e) { showError(e); } });
function getTodosForUser(userId) { // returns a promise for an array of the user's todos }
// Get (a promise for) the todos for user 123, and filter them
// using the isRecentTodo
filter.
var filteredTodos = getRecentTodosForUser(isRecentTodo, 123);
However, note the difference between func1
and func2
in the following:
var thrower = Promise.method(function(msg) { throw new Error(msg); });
var func1 = Promise.async(function *() { try { return thrower("hey"); } catch (e) { console.log("This line is never reached."); } });
var func2 = Promise.async(function *() { try { return (yield thrower("ho")); } catch (e) { console.log("Exception is caught here!", e); } });
When func1
returns a Promise
, we leave the scope of the try block. By the time the returned Promise
rejects with an error, we can no longer catch it.
If you want to ensure that rejected Promise
s get a chance to be caught, be sure to yield
them (which resolves the Promise
completely) before returning, as in func2
.
You can also use Promise.async
to implement coroutines:
function PingPong() { }
PingPong.prototype.ping = Promise.async(function* (val) { console.log("Ping?", val) yield Promise.delay(500) this.pong(val+1) });
PingPong.prototype.pong = Promise.async(function* (val) { console.log("Pong!", val) yield Promise.delay(500); this.ping(val+1) });
var a = new PingPong(); a.ping(0);
Running the example with node version at least 0.11.2
:
$ node --harmony test.js
Ping? 0
Pong! 1
Ping? 2
Pong! 3
Ping? 4
...
Tip
You can use Promise.join to wait for multiple promises at once.
You can combine it with ES6 destructuring for some neat syntax:
var getData = Promise.async(function* (urlA, urlB) { [resultA, resultB] = yield Promise.join(http.getAsync(urlA), http.getAsync(urlB)); //use resultA //use resultB });
You might wonder why not just do this?
var getData = Promise.async(function* (urlA, urlB) { var resultA = yield http.getAsync(urlA); var resultB = yield http.getAsync(urlB); });
The problem with the above is that the requests are not done in parallel. It will completely wait for request A to complete before even starting request B. In the example with Promise.join both requests fire off at the same time in parallel.
Legacy callbacks
For compatibility with legacy code which uses callbacks, you can use the optional cbArg
, as follows:
var getDataFor = Promise.async(function (input) { return dataFromDataBase(input); }, 1 / arg #1 is optional callback */);
/* Calling this using node 'callback' syntax */ getDataFor(input, function(err, dataForMe) { if (err) { console.error( err ); } else { console.log(dataForMe); } });
See also
Theasyncfunctions andawaitoperator available in Node >= 7.6
are compatible with Promise.async
andyield
. It should be straightforward to do a search-and-replace in your codebase once your minimum node version permits.
See also Q.async.
License
Copyright (c) 2014-2018 C. Scott Ananian
Portions are Copyright (c) 2014 Petka Antonov
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.