Promises were introduced as part of ECMAScript 2015 (ES6) and provide a cleaner way of working with asynchronous operations.
Modern JavaScript applications frequently need to perform actions that take time to complete, such as downloading data from a server, loading files or communicating with APIs.
Promises provide a structured way to handle these operations and are heavily used throughout modern JavaScript, Node.js and React applications.
What promises are
A Promise represents a value that may not be available immediately.
Instead of returning a result instantly, a Promise acts as a placeholder for a future value.
Consider ordering food in a restaurant. The order is placed immediately, but the meal takes time to prepare. A Promise works in a similar way.
Why promises are useful
Before Promises, asynchronous code was often handled using nested callbacks.
loadUser(function(user){ loadOrders(user, function(orders){ loadProducts(orders, function(products){ console.log(products); }); }); });
Deeply nested callbacks can become difficult to read and maintain.
Promises help organise asynchronous code and reduce excessive nesting.
Promise states
Every Promise exists in one of three states:
- Pending - The operation is still running.
- Fulfilled - The operation completed successfully.
- Rejected - The operation failed.
A Promise begins in the pending state and then becomes fulfilled or rejected.
Creating promises
A Promise can be created using the Promise constructor.
const promise = new Promise( (resolve, reject) => { } );
The constructor receives two callback functions named
resolve and reject.
Resolving promises
Calling resolve() indicates that the operation completed
successfully.
const promise = new Promise( (resolve, reject) => { resolve("Success"); } );
Once resolved, the Promise enters the fulfilled state.
Rejecting promises
Calling reject() indicates that an error has occurred.
const promise = new Promise( (resolve, reject) => { reject("An error occurred"); } );
Once rejected, the Promise enters the rejected state.
Using then()
The then() method executes code after a Promise has been fulfilled.
promise.then( result => { console.log(result); } );
The resolved value becomes available inside the callback function.
Using catch()
The catch() method handles rejected Promises.
promise.catch( error => { console.error(error); } );
This allows errors to be handled separately from successful results.
Using finally()
The finally() method executes regardless of whether a Promise
succeeds or fails.
promise.finally( () => { console.log("Finished"); } );
This is commonly used for cleanup operations.
Chaining promises
Multiple then() calls can be chained together.
promise .then(result => { return result.toUpperCase(); }) .then(result => { console.log(result); });
Each stage receives the value returned by the previous stage.
Promises and fetch()
One of the most common uses of Promises is the
fetch() function.
fetch("/users.json") .then(response => { return response.json(); }) .then(users => { console.log(users); });
The fetch() function returns a Promise because downloading data
can take time.
Promises in React
Promises are commonly used within React applications when loading data from APIs.
fetch("/api/users") .then(response => { return response.json(); }) .then(users => { console.log(users); });
Although modern React applications often use
async and await, these features are built on top of
Promises.
Understanding Promises makes it easier to understand asynchronous programming in React.
When to use promises
Promises should be used whenever an operation completes asynchronously.
Common examples include loading data from servers, reading files and communicating with APIs.
Since Promises are the foundation of modern asynchronous JavaScript, they
should be understood before moving on to async and
await.
