A Promise
is an object representing the eventual completion or failure of an asynchronous operation. Since most people are consumers of already-created promises, this guide will explain consumption of returned promises before explaining how to create them.
Essentially, a promise is a returned object to which you attach callbacks, instead of passing callbacks into a function.
Imagine a function, createAudioFileAsync()
, which asynchronously generates a sound file given a configuration record and two callback functions, one called if the audio file is successfully created, and the other called if an error occurs.
Here's some code that uses createAudioFileAsync()
:
function successCallback(result) {
console.log("Audio file ready at URL: " + result);
}
function failureCallback(error) {
console.error("Error generating audio file: " + error);
}
createAudioFileAsync (audioSettings, successCallback, failureCallback);
If createAudioFileAsync()
were rewritten to return a promise, you would attach your callbacks to it instead:
createAudioFileAsync(audioSettings).then(successCallback, failureCallback);
This convention has several advantages. We will explore each one.
Unlike old-fashioned passed-in callbacks, a promise comes with some guarantees:
- Callbacks added with
then()
will never be invoked before the completion of the current run of the JavaScript event loop. - These callbacks will be invoked even if they were added after the success or failure of the asynchronous operation that the promise represents.
- Multiple callbacks may be added by calling then() several times. They will be invoked one after another, in the order in which they were inserted.
One of the great things about using promises is chaining.
A common need is to execute two or more asynchronous operations back to back, where each subsequent operation starts when the previous operation succeeds, with the result from the previous step. We accomplish this by creating a promise chain.
Here's the magic: the then()
function returns a new promise, different from the original:
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);
or
const promise2 = doSomething().then( successCallback, failureCallback);
This second promise (promise2
) represents the completion not just of doSomething()
, but also of the successCallback
or failureCallback
you passed in, which can be other asynchronous functions returning a promise. When that's the case, any callbacks added to promise2 get queued behind the promise returned by either successCallback
or failureCallback
.
Basically, each promise represents the completion of another asynchronous step in the chain.
In the old days, doing several asynchronous operations in a row would lead to the classic callback pyramid of doom:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
With modern functions, we attach our callbacks to the returned promises instead, forming a promise chain:
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
The arguments to then
are optional, and catch(failureCallback)
is short for then(null, failureCallback)
. You might see this expressed with arrow functions instead:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result:${finalResult}`);
})
.catch(failureCallback);
Important: Always return results, otherwise callbacks won't catch the result of a previous promise (with arrow functions () => x
is short for () => { return x; })
.
Don't miss further informations about using promisses:
All the documentation in this page is taken from MDN