In JavaScript, promises
are a fundamental mechanism to facilitate asynchronous programming.
They allow us to manage operations that do not complete immediately (such as network requests, file reading operations, or timers) in a more structured and less error-prone way compared to callbacks.
A promise is like a “container” for a value that may be available now, in the future, or never (that’s why it’s called a promise)
In general, it is advisable to use async
/await
to work with promises in a more synchronous manner. This can make asynchronous code easier to understand and maintain.
How a Promise Works
A promise is an object that represents the possible success or failure of an asynchronous operation.
To reflect this, during its execution the promise has a property State
, which can be one of the following:
- Pending: Initial state. The asynchronous operation has not yet completed.
- Fulfilled: The operation has completed successfully, and the promise has a resulting value.
- Rejected: The operation failed, and the promise has a reason for the error.
Creating a Promise
We can create a promise using the Promise
constructor. This constructor takes a function (called executor) that receives two functions as arguments:
- resolve is called if the operation is successful, and the promise’s state changes to fulfilled.
- reject is called if there is an error, and the state changes to rejected.
These arguments are functions that are called to change the state of the promise. Let’s see it with an example,
const myPromise = new Promise((resolve, reject) => {
// Simulating an asynchronous operation
let success = true; // Simulates the outcome of the operation
if (success) {
resolve('Operation successful');
} else {
reject('Error in the operation');
}
});
In this example,
- We create a promise that receives its two functions
resolve
andreject
. - We simulate performing an operation with
success
. - Depending on the outcome of the operation, we invoke either
resolve
orreject
.
Handling Promises
To handle the result of a promise, we typically use the methods .then()
and .catch()
:
myPromise.then(onFulfilled, onRejected)
This method is called when the promise is fulfilled or rejected.
onFulfilled
is a function that is called with the value of the promise when it is fulfilled,onRejected
is called with the reason for rejection if the promise fails.
myPromise.catch(onRejected)
This method is used to handle errors. It is a shortcut for .then(null, onRejected)
and is called when the promise is rejected.
For example, let’s see an example of how a promise would typically be used,
myPromise
.then(result => {
console.log(result); // 'Operation successful'
})
.catch(error => {
console.error(error); // 'Error in the operation'
});
Chaining Promises
Promises allow chaining multiple .then()
to handle sequences of asynchronous operations.
Each .then()
returns a new promise, making it easier to compose asynchronous tasks.
myPromise
.then(result => {
console.log(result); // 'Operation successful'
return 'Another value';
})
.then(anotherResult => {
console.log(anotherResult); // 'Another value'
})
.catch(error => {
console.error(error);
});
Nested Promises
We could also nest promises within other promises to manage operations that depend on previous results.
myPromise
.then(result => {
console.log(result); // 'Operation successful'
return new Promise((resolve, reject) => {
setTimeout(() => resolve('Additional asynchronous operation'), 1000);
});
})
.then(additional => {
console.log(additional); // 'Additional asynchronous operation'
})
.catch(error => {
console.error(error);
});
However, this can lead to hard-to-read code, so it’s preferable to use chaining whenever possible.
Static Promise Methods
JavaScript provides several static methods on the Promise
object to handle multiple promises simultaneously.
These methods return a new promise, which is a composition of the promises passed as arguments.
Promise.all()
Promise.all()
takes an iterable of promises and returns a new promise that resolves when all promises in the iterable are fulfilled. If any promise is rejected, the returned promise will be rejected immediately.
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log(results); // [1, 2, 3]
})
.catch(error => {
console.error(error);
});
Promise.race()
Promise.race()
returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects.
const slowPromise = new Promise(resolve => setTimeout(() => resolve('Slow'), 2000));
const fastPromise = new Promise(resolve => setTimeout(() => resolve('Fast'), 1000));
Promise.race([slowPromise, fastPromise])
.then(result => {
console.log(result); // 'Fast'
});
Promise.allSettled()
Promise.allSettled()
returns a promise that resolves when all promises in the iterable have settled (whether they have been fulfilled or rejected).
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('Error');
const promise3 = Promise.resolve(3);
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
console.log(results);
// [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: 'Error'}, {status: 'fulfilled', value: 3}]
});
It returns an array of objects that describe the outcome of each promise.
Promise.any()
Promise.any()
returns a promise that resolves as soon as one of the promises in the iterable resolves.
const promise1 = Promise.reject('Error 1');
const promise2 = Promise.reject('Error 2');
const promise3 = Promise.resolve('Success');
Promise.any([promise1, promise2, promise3])
.then(result => {
console.log(result); // 'Success'
});
If all promises are rejected, the returned promise will be rejected with an AggregateError
.
Practical Examples
Example with fetch
The JavaScript fetch
API uses promises to handle HTTP requests. The fetch
function returns a promise that resolves when the response is available.
fetch('https://api.example.com/data')
.then(response => response.json()) // Converts the response to JSON
.then(data => {
console.log(data); // Displays the obtained data
})
.catch(error => {
console.error('Error in the request:', error);
});
Using Promises with Timers
You can also use promises to work with timers.
function wait(ms) {
return new Promise(resolve => {
setTimeout(() => {
resolve('Time elapsed');
}, ms);
});
}
wait(2000)
.then(message => {
console.log(message); // 'Time elapsed'
});