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 potential 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 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 changes the promise’s state to fulfilled.
- reject is called if there is an error, and changes the state 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
resolveandreject. - We simulate doing any operation with
exito. - Depending on the operation’s result, we invoke
resolveorreject.
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.
onFulfilledis a function called with the promise’s value when it is fulfilled,onRejectedis called with the rejection reason 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 normally 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() calls to handle sequences of asynchronous operations.
Each .then() returns a new promise, which facilitates composing 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 inside 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 is 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 we pass as arguments.
Promise.all()
Promise.all() receives 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 is 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 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 describing 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 is 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'
});
