As explained beautifully by Ilya Kantor in detail on https://javascript.info/promise-basics
The core idea behind promises is that a promise represents the result of an asynchronous operation. A promise is in one of three different states:
- pending - The initial state of a promise.
- fulfilled - The state of a promise representing a successful operation.
- rejected - The state of a promise representing a failed operation.
Once a promise is fulfilled or rejected, it is immutable (i.e. it can never change again).
The constructor syntax for a promise object is:
let promise = new Promise(function(resolve, reject) {
// executor (the producing code, "singer")
});
The function passed to
new Promise is called the executor. When the promise is created, this executor function runs automatically. It contains the producing code, that should eventually produce a result.
The resulting
promise object has internal properties:state— initially “pending”, then changes to either “fulfilled” or “rejected”,result— an arbitrary value of your choosing, initiallyundefined.
When the executor finishes the job, it should call one of the functions that it gets as arguments:
resolve(value)— to indicate that the job finished successfully:- sets
stateto"fulfilled", - sets
resulttovalue.
- sets
reject(error)— to indicate that an error occurred:- sets
stateto"rejected", - sets
resulttoerror.
- sets
To summarize, the executor should do a job (something that takes time usually) and then call
resolve or reject to change the state of the corresponding Promise object.
The Promise that is either resolved or rejected is called “settled”, as opposed to a “pending” Promise.
Consumers: then, catch, finally
A Promise object serves as a link between the executor (the “producing code” or “singer”) and the consuming functions (the “fans”), which will receive the result or error. Consuming functions can be registered (subscribed) using methods
.then, .catch and .finally.then
The most important, fundamental one is
.then.
The syntax is:
promise.then(function(result){/* handle a successful result */},function(error){/* handle an error */});
The first argument of
.then is a function that:- runs when the Promise is resolved, and
- receives the result.
The second argument of
.then is a function that:- runs when the Promise is rejected, and
- receives the error.
For instance, here’s a reaction to a successfully resolved promise:
letpromise=newPromise(function(resolve,reject){setTimeout(()=>resolve("done!"),1000);});// resolve runs the first function in .thenpromise.then(result=>alert(result),// shows "done!" after 1 seconderror=>alert(error)// doesn't run);
The first function was executed.
And in the case of a rejection – the second one:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// reject runs the second function in .then
promise.then(
result => alert(result), // doesn't run
error => alert(error) // shows "Error: Whoops!" after 1 second
);
catch
If we’re interested only in errors, then we can use
null as the first argument: .then(null, errorHandlingFunction). Or we can use .catch(errorHandlingFunction), which is exactly the same:letpromise=newPromise((resolve,reject)=>{setTimeout(()=>reject(newError("Whoops!")),1000);});// .catch(f) is the same as promise.then(null, f)promise.catch(alert);// shows "Error: Whoops!" after 1 second
The call
.catch(f) is a complete analog of .then(null, f), it’s just a shorthand.finally
Just like there’s a
finally clause in a regular try {...} catch {...}, there’s finally in promises.
The call
.finally(f) is similar to .then(f, f) in the sense that it always runs when the promise is settled: be it resolve or reject.finally is a good handler for performing cleanup, e.g. stopping our loading indicators, as they are not needed any more, no matter what the outcome is.
Like this:
newPromise((resolve,reject)=>{/* do something that takes time, and then call resolve/reject */})// runs when the promise is settled, doesn't matter successfully or not.finally(()=>stop loading indicator).then(result=>show result,err=>show error)
It’s not exactly an alias though. There are several important differences:
- A
finallyhandler has no arguments. Infinallywe don’t know whether the promise is successful or not. That’s all right, as our task is usually to perform “general” finalizing procedures. - Finally passes through results and errors to the next handler.For instance, here the result is passed through
finallytothen:newPromise((resolve,reject)=>{setTimeout(()=>resolve("result"),2000)}).finally(()=>alert("Promise ready")).then(result=>alert(result));// <-- .then handles the resultAnd here there’s an error in the promise, passed throughfinallytocatch:newPromise((resolve,reject)=>{thrownewError("error");}).finally(()=>alert("Promise ready")).catch(err=>alert(err));// <-- .catch handles the error object
Promise API
There are 4 static methods in the
Promise class. We’ll quickly cover their use cases here.Promise.resolve
The syntax:
let promise = Promise.resolve(value);
Returns a resolved promise with the given
value.Promise.reject
The syntax:
let promise = Promise.reject(error);
Create a rejected promise with the
error.Promise.all
Let’s say we want to run many promises to execute in parallel, and wait till all of them are ready.
For instance, download several URLs in parallel and process the content when all are done.
That’s what
Promise.all is for.
The syntax is:
let promise = Promise.all([...promises...]);
It takes an array of promises (technically can be any iterable, but usually an array) and returns a new promise.
The new promise resolves when all listed promises are settled and has an array of their results.
For instance, the
Promise.all below settles after 3 seconds, and then its result is an array [1, 2, 3]:Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member
Please note that the relative order is the same. Even though the first promise takes the longest time to resolve, it is still first in the array of results.
A common trick is to map an array of job data into an array of promises, and then wrap that into
Promise.all.
For instance, if we have an array of URLs, we can fetch them all like this:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// map every url to the promise fetch(github url)
let requests = urls.map(url => fetch(url));
// Promise.all waits until all jobs are resolved
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
Promise.race
Similar to
Promise.all, it takes an iterable of promises, but instead of waiting for all of them to finish, it waits for the first result (or error), and goes on with it.
The syntax is:
let promise = Promise.race(iterable);
For instance, here the result will be
1:Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
Summary
There are 4 static methods of
Promise class:Promise.resolve(value)– makes a resolved promise with the given value.Promise.reject(error)– makes a rejected promise with the given error.Promise.all(promises)– waits for all promises to resolve and returns an array of their results. If any of the given promises rejects, then it becomes the error ofPromise.all, and all other results are ignored.Promise.race(promises)– waits for the first promise to settle, and its result/error becomes the outcome.