This blog post is outdated. Please read chapter “Promises for asynchronous programming” in “Exploring ES6”.
This blog post is an introduction to asynchronous programming via promises in general and the ECMAScript 6 (ES6) promise API in particular. It is second in a series of two posts – part one explains foundations of asynchronous programming (which you may need to learn in order to fully understand this post).
Given that the ECMAScript 6 promise API is easy to polyfill for ECMAScript 5, I’m mainly using function expressions and not ECMAScript 6 arrow functions, even though the latter are much less verbose.
Promises are a pattern that helps with one particular kind of asynchronous programming: functions (or methods) that return their results asynchronously. To implement such a function, you return a promise, an object that is a placeholder for the result. The caller of the function registers callbacks with the promise to be notified once the result has been computed. The function sends the result via the promise.
The de-facto standard for JavaScript promises is called Promises/A+ [1]. The ECMAScript 6 promise API follows that standard.
Let’s look at a first example, to give you a taste of what working with promises is like.
With Node.js-style callbacks, reading a file asynchronously looks like this:
fs.readFile('config.json',
function (error, text) {
if (error) {
console.error('Error while reading config file');
} else {
try {
var obj = JSON.parse(text);
console.log(JSON.stringify(obj, null, 4));
} catch (e) {
console.error('Invalid JSON in file');
}
}
});
With promises, the same functionality is implemented like this:
readFilePromisified('config.json')
.then(function (text) { // (A)
var obj = JSON.parse(text);
console.log(JSON.stringify(obj, null, 4));
})
.catch(function (reason) { // (B)
// File read error or JSON SyntaxError
console.error('An error occurred', reason);
});
There are still callbacks, but they are provided via methods that are invoked on the result (then()
and catch()
). The error callback in line (B) is convenient in two ways: First, it’s a single style of handling errors. Second, you can handle the errors of both readFilePromisified()
and the callback from line (A).
Let’s look at how promises are operated from the producer and the consumer side.
As a producer, you create a promise and send a result via it:
var promise = new Promise(
function (resolve, reject) { // (A)
...
if (...) {
resolve(value); // success
} else {
reject(reason); // failure
}
});
A promise is always in either one of three (mutually exclusive) states:
A promise is settled (the computation it represents has finished) if it is either fulfilled or rejected. A promise can only be settled once and then stays settled. Subsequent attempts to settle it have no effect.
The parameter of new Promise()
(starting in line (A)) is called an executor:
resolve()
. That usually fulfills the promise (it may not, if you resolve with a promise, as explained later).reject()
. That always rejects the promise.As a consumer of promise
, you are notified of a fulfillment or a rejection via reactions – callbacks that you register with the method then()
:
promise.then(
function (value) { /* fulfillment */ },
function (reason) { /* rejection */ }
);
What makes promises so useful for asynchronous functions (with one-off results) is that once a promise is settled, it doesn’t change anymore. Furthermore, there are never any race conditions, because it doesn’t matter whether you invoke then()
before or after a promise is settled:
If you are only interested in fulfillments, you can omit the second parameter of then()
:
promise.then(
function (value) { /* fulfillment */ }
);
If you are only interested in rejections, you can omit the first parameter. The method catch()
is a more compact way of doing the same thing.
promise.then(
null,
function (reason) { /* rejection */ }
);
// Equivalent:
promise.catch(
function (reason) { /* rejection */ }
);
It is recommended to use then()
exclusively for fulfillments and catch()
for errors, because it nicely labels callbacks and because you can handle the rejections of multiple promises at the same time (how is explained later).
Let’s use these basic building blocks in a few examples.
The following is a promise-based function that performs an HTTP GET via the event-based XMLHttpRequest API:
function httpGet(url) {
return new Promise(
function (resolve, reject) {
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (this.status === 200) {
// Success
resolve(this.response);
} else {
// Something went wrong (404 etc.)
reject(new Error(this.statusText));
}
}
request.onerror = function () {
reject(new Error(
'XMLHttpRequest Error: '+this.statusText));
};
request.open('GET', url);
request.send();
});
}
This is how you use httpGet()
:
httpGet('http://example.com/file.txt')
.then(
function (value) {
console.log('Contents: ' + value);
},
function (reason) {
console.error('Something went wrong', reason);
});
Let’s implement setTimeout()
as the promise-based function delay()
(similar to Q.delay()
).
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms); // (A)
});
}
// Using delay():
delay(5000).then(function () { // (B)
console.log('5 seconds have passed!')
});
Note that in line (A), we are calling resolve
with zero parameters, which is the same as calling resolve(undefined)
. We don’t need the fulfillment value in line (B), either and simply ignore it. Just being notified is enough here.
function timeout(ms, promise) {
return new Promise(function (resolve, reject) {
promise.then(resolve);
setTimeout(function () {
reject(new Error('Timeout after '+ms+' ms')); // (A)
}, ms);
});
}
Note that the rejection after the timeout (in line (A)) does not cancel the request, but it does prevent the promise being fulfilled with its result.
Using timeout()
looks like this:
timeout(5000, httpGet('http://example.com/file.txt'))
.then(function (value) {
console.log('Contents: ' + value);
})
.catch(function (reason) {
console.error('Error or timeout', reason);
});
then()
The result of a method call
P.then(onFulfilled, onRejected)
is a new promise Q. That means that you can keep the promised-based control flow going by invoking then()
on Q:
onFulfilled
or onRejected
.onFulfilled
or onRejected
throw an exception.If you resolve the promise Q returned by then()
with a normal value, you can pick up that value via a subsequent then()
:
asyncFunc()
.then(function (value1) {
return 123;
})
.then(function (value2) {
console.log(value2); // 123
});
You can also resolve the promise Q returned by then()
with a thenable R. A thenable is any object that has a promise-style method then()
. Thus, promises are thenable. Resolving with R (e.g. by returning it from onFulfilled
) means that it is inserted “after” Q: R’s settlement is forwarded to Q’s onFulfilled
and onRejected
callbacks. In a way, Q becomes R.
The main use for this mechanism is to flatten nested then()
calls, like in the following example:
asyncFunc1()
.then(function (value1) {
asyncFunc2()
.then(function (value2) {
...
});
})
The flat version looks like this:
asyncFunc1()
.then(function (value1) {
return asyncFunc2();
})
.then(function (value2) {
...
})
As mentioned previously, whatever you return in an error handler becomes a fulfillment value (not rejection value!). That allows you to specify default values that are used in case of failure:
retrieveFileName()
.catch(function () {
// Something went wrong, use a default value
return 'Untitled.txt';
})
.then(function (fileName) {
...
});
Exceptions in the executor are passed on to the next error handler.
new Promise(function (resolve, reject) {
throw new Error();
})
.catch(function (err) {
// Handle error here
});
As are exceptions that are thrown in either one of then
’s parameters:
asyncFunc()
.then(function (value) {
throw new Error();
})
.catch(function (reason) {
// Handle error here
});
There can be one or more then()
method calls that don’t provide an error handler. Then the error is passed on until there is an error handler.
asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.catch(function (reason) {
// Something went wrong above
});
This section describes how you can compose existing promises to create new ones. We have already encountered one way of composing promises: sequential chaining via then()
. Promise.all()
and Promise.race()
provide additional ways of composing.
map()
via Promise.all()
One nice thing about promises is that many synchronous tools still work, because promise-based functions return results. For example, you can use the array method map()
:
var fileUrls = [
'http://example.com/file1.txt',
'http://example.com/file2.txt'
];
var promisedTexts = fileUrls.map(httpGet);
promisedTexts
is an array of promises. Promise.all()
takes an array of promises (thenables and other values are converted to promises via Promise.resolve()
) and, once all of them are fulfilled, it fulfills with an array of their values:
Promise.all(promisedTexts)
.then(function (texts) {
texts.forEach(function (text) {
console.log(text);
});
})
.catch(function (reason) {
// Receives first rejection among the promises
});
Promise.race()
Promise.race()
takes an array of promises (thenables and other values are converted to promises via Promise.resolve()
) and returns a promise P. The first of the input promises that is settled passes its settlement on to the output promise.
As an example, let’s use Promise.race()
to implement a timeout:
Promise.race([
httpGet('http://example.com/file.txt'),
delay(5000).then(function () {
throw new Error('Timed out')
});
])
.then(function (text) { ... })
.catch(function (reason) { ... });
A promise library has complete control over whether results are delivered to promise reactions synchronously (right away) or asynchronously (after the current continuation, the current piece of code, is finished). However, the Promises/A+ specification demands that the latter mode of execution be always used. It states so via the following requirement (2.2.4) for the then()
method:
onFulfilled
oronRejected
must not be called until the execution context stack contains only platform code.
That means that you code can rely on run-to-completion semantics (as explained in part 1) and that chaining promises won’t starve other tasks of processing time.
This section gives an overview of the ECMAScript 6 promise API, as described in the specification.
The promise API is about delivering results asynchronously. A promise object (short: promise) is a stand-in for the result, which is delivered via that object.
States:
Reacting to state changes:
Promise reactions are callbacks that you register with the promise method then()
, to be notified of a fulfillment or a rejection.
A thenable is an object that has a promise-style then()
method. Whenever the API is only interested in being notified of settlements, it only demands thenables.
Changing states: There are two operations for changing the state of a promise. After you have invoked either one of them once, further invocations have no effect.
The constructor for promises has the following signature:
var p = new Promise(executor(resolve, reject))
It creates a promise whose behavior is determined by the callback executor
. It can use its parameters to resolve or reject p
:
resolve(x)
resolves p
with x
:
x
is thenable, its settlement is forwarded to p
(which includes triggering reactions registered via then()
).p
is fulfilled with x
.reject(e)
rejects p
with the value e
(often an instance of Error
).All static methods of Promise
support subclassing: they create new instances via their receiver (think: new this(...)
) and also access other static methods via it (this.resolve(...)
versus Promise.resolve(...)
).
The following two methods create new instances of their receiver (their this
).
Promise.resolve(x)
:
x
is thenable, it is converted to a promise (an instance of the receiver).x
is a promise, it is returned unchanged.x
.Promise.reject(reason)
: creates a new promise that is rejected with the value reason
.
Intuitively, the static methods Promise.all()
and Promise.race()
compose iterables of promises to a single promise. That is:
this.resolve()
.The methods are:
Promise.all(iterable)
: returns a promise that…
iterable
are fulfilled.Promise.race(iterable)
: the first element of iterable
that is settled is used to settle the returned promise.
Promise.prototype.then(onFulfilled, onRejected)
:
onFulfilled
and onRejected
are called reactions.onFulfilled
is called immediately if the promise is already fulfilled or as soon as it becomes fulfilled. Similarly, onRejected
is informed of rejections.then()
returns a new promise Q (created via the constructor of the receiver):
onFulfilled
has been omitted, a fulfillment of the receiver is forwarded to the result of then()
.onRejected
has been omitted, a rejection of the receiver is forwarded to the result of then()
.Default values for omitted reactions could be implemented like this:
function defaultOnFulfilled(x) {
return x;
}
function defaultOnRejected(e) {
throw e;
}
Promise.prototype.catch(onRejected)
:
then(null, onRejected)
.One important advantage of promises is that they will increasingly be used by asnychronous browser APIs and unify currently diverse and incompatible patterns and conventions. Let’s look at two upcoming promise-based APIs.
The fetch API is a promise-based alternative to XMLHttpRequest:
fetch(url)
.then(request => request.text())
.then(str => ...)
fetch()
returns a promise for the actual request, text()
returns a promise for the content as a string.
The ECMAScript 6 API for programmatically importing modules is based on promises, too:
System.import('some_module.js')
.then(some_module => {
...
})
Compared to events, promises are better for handling one-off results. It doesn’t matter whether you register for a result before or after it has been computed, you will get it. This advantage of promises is fundamental in nature. On the flip side, you can’t use them for handling recurring events. Chaining is another advantage of promises, but one that could be added to event handling.
Compared to callbacks, promises have cleaner function (or method) signatures. With callbacks, parameters are used for input and output:
fs.readFile(name, opts?, function (err, data))
With promises, all parameters are used for input:
readFilePromisified(name, opts?)
.then(dataHandler, errorHandler)
Additional promise advantages include better error handling (which integrates exceptions) and easier composition (because you can reuse some synchronous tools such as Array.prototype.map()
).
Promises work well for for single asynchronous results. They are not suited for:
ECMAScript 6 promises lack two features that are sometimes useful:
The Q promise library has support for the latter and there are plans to add both capabilities to Promises/A+.
With the help of a utility function such as Q.spawn()
, you can use promise-based functions inside shallow coroutines, implemented via generators. This has the important advantage that the code looks synchronous and that you can use synchronous mechanisms such a try-catch
:
Q.spawn(function* () {
try {
let [foo, bar] = yield Promise.all([ // (A)
httpGet('foo.json'),
httpGet('bar.json')
]);
render(foo);
render(bar);
} catch (e) {
console.log('Read failed: ' + e);
}
});
The parameter of Q.spawn()
is a generator function [2]. If the yield
operator is used, the following things happen:
yield
is “returned” by the function. (It’s not exactly a “return”, but ignore that for now.)yield
returns the value. In the latter case, an exception is thrown inside the function, as if it were thrown “inside” yield
’s operand.Thus, it’s clear what Q.spawn()
has to do: When the generator function yields a promise, spawn
registers reactions and waits for a settlement. If the promise is fulfilled, the generator is resumed with the result. If the promise is rejected, an exception is thrown inside the generator.
There is a proposal to add support for spawning to JavaScript, via the new syntactic construct “async functions”. The previous example as an async function looks as follows. Under the hood, there is not much of a difference – async functions are based on generators.
async function () {
try {
let [foo, bar] = await Promise.all([
httpGet('foo.json'),
httpGet('bar.json')
]);
render(foo);
render(bar);
} catch (e) {
console.log('Read failed: ' + e);
}
}
The main challenge with debugging asynchronous code is that it contains asynchronous function and method calls. Asynchronous calls originate in one task and are carried out in a new task. If something goes wrong in the new task, a stack trace will only cover that task and not contain information about previous tasks. Thus, you have to make do with much less debugging information in asynchronous programming.
Google Chrome recently got the ability to debug asynchronous code [3]. It doesn’t completely support promises, yet, but it’s impressive how well it handles normal asynchronous calls. For example, in the following code, first
asynchronously calls second
which in turn calls third
.
function first() {
setTimeout(function () { second('a') }, 0); // (A)
}
function second(x) {
setTimeout(function () { third('b') }, 0); // (B)
}
function third(x) {
debugger;
}
first();
As you can see in the screen shot, the debugger shows a stack trace that contains all three functions. It even includes the anonymous functions in line (A) and (B).
In this section, we will approach promises from a different angle: Instead of learning how to use the API, we will look at a simple implementation of it. This different angle helped me greatly with making sense of promises.
The promise implementation is called DemoPromise and available on GitHub. In order to be easier to understand, it doesn’t completely match the API. But it is close enough to still give you much insight into the challenges that actual implementations are facing.
DemoPromise
is a constructor with three instance prototype methods:
DemoPromise.prototype.resolve(value)
DemoPromise.prototype.reject(reason)
DemoPromise.prototype.then(onFulfilled, onRejected)
That is, resolve
and reject
are methods (versus functions handed to a callback parameter of the constructor).
Our first implementation is a stand-alone promise with minimal functionality:
then()
. The method does not support chaining, yet – it does not return anything. It must work independently of whether the promise has already been settled or not.This is how this first implementation is used:
var dp = new DemoPromise();
dp.resolve('abc');
dp.then(function (value) {
console.log(value); // abc
});
The following diagram illustrates how our first DemoPromise
works:
Let’s examine then()
first. It has to handle two cases:
onFulfilled
and onRejected
, to be used when the promise is settled.onFulfilled
or onRejected
can be invoked right away.DemoPromise.prototype.then = function (onFulfilled, onRejected) {
var self = this;
var fulfilledTask = function () {
onFulfilled(self.promiseResult);
};
var rejectedTask = function () {
onRejected(self.promiseResult);
};
switch (this.promiseState) {
case 'pending':
this.fulfillReactions.push(fulfilledTask);
this.rejectReactions.push(rejectedTask);
break;
case 'fulfilled':
addToTaskQueue(fulfilledTask);
break;
case 'rejected':
addToTaskQueue(rejectedTask);
break;
}
};
function addToTaskQueue(task) {
setTimeout(task, 0);
}
resolve()
works as follows: If the promise is already settled, it does nothing (ensuring that a promise can only be settled once). Otherwise, the state of the promise changes to 'fulfilled'
and the result is cached in this.promiseResult
. All fulfillment reactions that have been enqueued so far must be triggered now.
Promise.prototype.resolve = function (value) {
if (this.promiseState !== 'pending') return;
this.promiseState = 'fulfilled';
this.promiseResult = value;
this._clearAndEnqueueReactions(this.fulfillReactions);
return this; // enable chaining
};
Promise.prototype._clearAndEnqueueReactions = function (reactions) {
this.fulfillReactions = undefined;
this.rejectReactions = undefined;
reactions.map(addToTaskQueue);
};
reject()
is similar to resolve()
.
The next feature we implement is chaining:
then()
returns a promise that is resolved with what either onFulfilled
or onRejected
return.onFulfilled
or onRejected
are missing, whatever they would have received is passed on to the promise returned by then()
.Obviously, only then()
changes:
DemoPromise.prototype.then = function (onFulfilled, onRejected) {
var returnValue = new DemoPromise(); // (A)
var self = this;
var fulfilledTask;
if (typeof onFulfilled === 'function') {
fulfilledTask = function () {
var r = onFulfilled(self.promiseResult);
returnValue.resolve(r); // (B)
};
} else {
fulfilledTask = function () {
returnValue.resolve(self.promiseResult); // (C)
};
}
var rejectedTask;
if (typeof onRejected === 'function') {
rejectedTask = function () {
var r = onRejected(self.promiseResult);
returnValue.resolve(r); // (D)
};
} else {
rejectedTask = function () {
// Important: we must reject here!
// Normally, result of `onRejected` is used to resolve
returnValue.reject(self.promiseResult); // (E)
};
}
...
return returnValue; // (F)
};
then()
creates and returns a new promise (lines (A) and (F)). Additionally, fulfilledTask
and rejectedTask
are set up differently: After a settlement...
The result of onFulfilled
is used to resolve returnValue
(line (B).
onFulfilled
is missing, we use the fulfillment value to resolve returnValue
(line (C)).The result of onRejected
is used to resolve (not reject!) returnValue
(line (D)).
onRejected
is missing, we use the rejection value to reject returnValue
(line (E)).Flattening is mostly about making chaining more convenient: Normally, returning a value from a reaction passes it on to the next then()
. If we return a promise, it would be nice if it could be “unwrapped” for us, like in the following example:
asyncFunc1()
.then(function (value1) {
return asyncFunc2(); // (A)
})
.then(function (value2) {
// value2 is fulfillment value of asyncFunc2() promise
console.log(value2);
});
We returned a promise in line (A) and didn’t have to nest a call to then()
inside the current method, we could invoke then()
on the method’s result. Thus: no nested then()
, everything remains flat.
We implement this by letting the resolve()
method do the flattening:
We can make flattening more generic if we allow Q to be a thenable (instead of only a promise).
To implement locking-in, we introduce a new boolean flag this.alreadyResolved
. Once it is true, this
is locked and can’t be resolved anymore. Note that this
may still be pending, because its state is now the same as the promise it is locked in on.
DemoPromise.prototype.resolve = function (value) {
if (this.alreadyResolved) return;
this.alreadyResolved = true;
this._doResolve(value);
return this; // enable chaining
};
The actual resolution now happens in the private method _doResolve()
:
DemoPromise.prototype._doResolve = function (value) {
var self = this;
// Is `value` a thenable?
if (value !== null && typeof value === 'object'
&& 'then' in value) {
addToTaskQueue(function () { // (A)
value.then(
function onFulfilled(value) {
self._doResolve(value);
},
function onRejected(reason) {
self._doReject(reason);
});
});
} else {
this.promiseState = 'fulfilled';
this.promiseResult = value;
this._clearAndEnqueueReactions(this.fulfillReactions);
}
};
The flattening is performed in line (A): If value
is fulfilled, we want self
to be fulfilled and if value
is rejected, we want self
to be rejected. The forwarding happens via the private methods _doResolve
and _doReject
, to get around the protection via alreadyResolved
.
With chaining, the states of promises become more complex (as covered by Sect. 25.4 of the ECMAScript 6 specification):
If you are only using promises, you can normally adopt a simplified worldview and ignore locking-in. The most important state-related concept remains “settledness”: a promise is settled if it is either fulfilled or rejected. After a promise is settled, it doesn’t change, anymore (state and fulfillment or rejection value).
If you want to implement promises then “resolving” matters, too and is now harder to understand:
As our final feature, we’d like our promises to handle exceptions in user code as rejections. For now, “user code” means the two callback parameters of then()
.
The following excerpt shows how we turn exceptions inside onFulfilled
into rejections – by wrapping a try-catch
around its invocation in line (A).
var fulfilledTask;
if (typeof onFulfilled === 'function') {
fulfilledTask = function () {
try {
var r = onFulfilled(self.promiseResult); // (A)
returnValue.resolve(r);
} catch (e) {
returnValue.reject(e);
}
};
} else {
fulfilledTask = function () {
returnValue.resolve(self.promiseResult);
};
}
If we wanted to turn DemoPromise
into an actual promise implementation, we’d still need to implement the revealing constructor pattern [4]: ES6 promises are not resolved and rejected via methods, but via functions that are handed to the executor, the callback parameter of the constructor.
If the executor throws an exception then “its” promise must be rejected.
This section describes two useful methods that are easy to add to ES6 promises. Many of the more comprehensive promise libraries have them.
done()
When you chain several promise method calls, you risk silently discarding errors. For example:
function doSomething() {
asyncFunc()
.then(f1)
.catch(r1)
.then(f2); // (A)
}
If then()
in line (A) produces a rejection, it will never be handled anywhere. The promise library Q provides a method done()
, to be used as the last element in a chain of method calls. It either replaces the last then()
(and has one to two arguments):
function doSomething() {
asyncFunc()
.then(f1)
.catch(r1)
.done(f2);
}
Or it is inserted after the last then()
(and has zero arguments):
function doSomething() {
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
}
Quoting the Q documentation:
The Golden Rule of
done
vs.then
usage is: either return your promise to someone else, or if the chain ends with you, calldone
to terminate it. Terminating withcatch
is not sufficient because the catch handler may itself throw an error.
This is how you would implement done()
in ECMAScript 6:
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
setTimeout(() => { throw reason }, 0);
});
};
While done
’s functionality is clearly useful, it has not been added to ECMAScript 6, because this kind of check can be performed automatically by debuggers in the future (a thread on es-discuss provides further background).
finally()
Sometimes you want to perform an action independently of whether an error happened or not. For example, to clean up after you are done with a resource. That’s what the promise method finally()
is for, which works much like the finally
clause in exception handling. Its callback receives no arguments, but is notified of either a resolution or a rejection.
createResource(...)
.then(function (value1) {
// Use resource
})
.then(function (value2) {
// Use resource
})
.finally(function () {
// Clean up
});
This is how Domenic Denicola
proposes to implement finally()
:
Promise.prototype.finally = function (callback) {
let p = this.constructor;
// We don’t invoke the callback in here,
// because we want then() to handle its exceptions
return this.then(
// Callback fulfills: pass on predecessor settlement
// Callback rejects: pass on rejection (=omit 2nd arg.)
value => p.resolve(callback()).then(() => value),
reason => p.resolve(callback()).then(() => { throw reason })
);
};
The callback determines how the settlement of the receiver (this
) is handled:
finally()
. In a way, we take finally()
out of the chain of methods.Example 1 (by Jake Archibald): using finally()
to hide a spinner. Simplified version:
showSpinner();
fetchGalleryData()
.then(data => updateGallery(data))
.catch(showNoDataError)
.finally(hideSpinner);
Example 2 (by Kris Kowal): using finally()
to tear down a test.
var HTTP = require("q-io/http");
var server = HTTP.Server(app);
return server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
There are many promise libraries out there. The following ones conform to the ECMAScript 6 API, which means that you can use them now and easily migrate to native ES6 later.
Q.Promise
by Kris Kowal implements the ES6 API.Promise
.When you are using a promise library, you sometimes need to use non-promise-based asynchronous code. This section explains how to do that for Node.js-style asynchronous functions and jQuery deferreds.
The promise library Q has several tool functions for converting functions that use Node.js-style (err,result)
callbacks to ones that return a promise (there are even functions that do the opposite – convert promise-based functions to ones that accept callbacks). For example:
var readFile = Q.denodeify(FS.readFile);
readFile('foo.txt', 'utf-8')
.then(function (text) {
...
});
denodify is a micro-library that only provides the “nodification” functionality and complies with the ECMAScript 6 promise API.
jQuery has deferreds which are similar to promises, but have several differences that prevent compatibility. Their method then()
is almost like that of ES6 promises (main difference: it doesn’t catch errors in reactions). Thus, we can convert a jQuery deferred to an ES6 promise via Promise.resolve()
:
Promise.resolve(
jQuery.ajax({
url: 'somefile.html',
type: 'GET'
}))
.then(function (data) {
console.log(data);
})
.catch(function (reason) {
console.error(reason);
});
“Promises/A+”, edited by Brian Cavalier and Domenic Denicola (the de-facto standard for JavaScript promises) ↩︎
“Debugging Asynchronous JavaScript with Chrome DevTools” by Pearl Chen ↩︎
“The Revealing Constructor Pattern” by Domenic Denicola (this pattern is used by the Promise
constructor) ↩︎