How to Use JavaScript Promises
Published June 19, 2024 at 7:34 pm

Understanding JavaScript Promises: An Introduction
Are you struggling to manage asynchronous operations in JavaScript?
Let promises solve your problem by providing a robust and efficient way to handle async code.
Promises allow you to write cleaner and more readable code, eliminating the callback hell that can make complex operations hard to manage.
TLDR: How Do You Use JavaScript Promises?
A Promise in JavaScript is an object representing the eventual completion or failure of an asynchronous operation.
Here’s a quick example:
const myPromise = new Promise((resolve, reject) => {
if (/* some condition */) {
resolve('Success!');
} else {
reject('Failure!');
}
});
myPromise
.then((value) => {
console.log(value); // 'Success!'
})
.catch((error) => {
console.log(error); // 'Failure!'
});
In this example, myPromise
either resolves with the value ‘Success!’ or rejects with the error ‘Failure!’.
Creating JavaScript Promises
To create a promise, use the Promise
constructor.
It takes a function with two arguments: resolve
and reject
.
Resolved promises signify a successful completion, while rejected promises denote an error or a failure.
const myPromise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("Operation succeeded.");
} else {
reject("Operation failed.");
}
});
In the above code snippet, we create a new promise named myPromise
.
Inside the promise, we check the success
variable.
If it is true
, the promise resolves with the message “Operation succeeded.”
If false
, it rejects with the message “Operation failed.”
Consuming JavaScript Promises: Then and Catch
Promises have two primary methods: then
and catch
.
The then
method is used to handle resolved promises.
The catch
method handles rejections.
myPromise
.then((message) => {
console.log(message); // Operation succeeded.
})
.catch((message) => {
console.log(message); // Operation failed.
});
In this code, if the promise is resolved, then
logs “Operation succeeded.”
If rejected, catch
logs “Operation failed.”
Chaining Promises for Sequential Execution
Promises can be chained, meaning one can follow another.
Chaining allows for sequential execution of asynchronous tasks.
fetchData()
.then((data) => process(data))
.then((processedData) => save(processedData))
.catch((error) => console.log('Error:', error));
In this example, fetchData()
, process(data)
, and save(processedData)
are functions that return promises.
The chained then
calls ensure each function runs sequentially.
Promise.all for Concurrent Execution
Sometimes, you need to run multiple promises concurrently.
Promise.all allows you to do exactly that.
const promise1 = Promise.resolve('First data');
const promise2 = Promise.resolve('Second data');
const promise3 = Promise.resolve('Third data');
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // ['First data', 'Second data', 'Third data']
})
.catch((error) => {
console.log('Error:', error);
});
In this example, promise1
, promise2
, and promise3
execute concurrently.
The then method logs an array with the results of each promise upon their completion.
If any promise rejects, catch will handle the error.
Promise.race for Competitive Execution
Often, you want to proceed with the fastest promise.
Promise.race helps you achieve that.
const slowPromise = new Promise((resolve) =>
setTimeout(resolve, 1000, 'Slow data')
);
const fastPromise = new Promise((resolve) =>
setTimeout(resolve, 500, 'Fast data')
);
Promise.race([slowPromise, fastPromise])
.then((value) => {
console.log(value); // Fast data
})
.catch((error) => {
console.log('Error:', error);
});
In this code, Promise.race
runs slowPromise
and fastPromise
concurrently.
FastPromise completes first, so its result “Fast data” is logged.
Error Handling in Promises
Handling errors in promises is essential to ensure your code’s robustness.
Using the catch
method is a common way to manage errors.
const faultyPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Something went wrong.');
}, 1000);
});
faultyPromise
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error); // Something went wrong.
});
In this example, faultyPromise
rejects with an error message after one second.
The catch method logs the error.
Combining Async/Await with Promises
Async/await is a syntactical sugar over promises, making code easier to read and write.
It is built on top of promises.
An async function always returns a promise.
async function fetchData() {
try {
let response = await fetch('https://api.example.com/data');
let data = await response.json();
console.log(data);
} catch (error) {
console.log('Error:', error);
}
}
fetchData();
In this code, fetchData
is an async function.
Await pauses the execution until the promise resolves or rejects.
If an error occurs, catch will handle it.
Advantages and Disadvantages of Using Promises
Pros:
- More manageable code.
- Better error handling.
- Avoids callback hell.
Cons:
- Can still get complex for advanced use cases.
- Requires understanding of the promise chain and async/await syntax.
Common Issues and Solutions
Unintended early execution:
This happens when you accidentally invoke the function instead of passing it.
Whenever working with promises, always pass the function reference to then
or catch
, don’t invoke it.
Multiple states:
A promise can only resolve or reject once.
Make sure you are not calling resolve or reject multiple times.
Uncaught promise rejections:
Always add a catch
block to your promises to handle potential rejections.
Frequently Asked Questions
What is a promise in JavaScript?
A promise in JavaScript is an object that represents the eventual completion or failure of an asynchronous operation.
How do you create a promise?
To create a promise, use the Promise
constructor which takes a function with two arguments: resolve
and reject
.
How do you consume a promise?
Consume a promise using its then
method to handle successful completion and catch
for errors.
What are the methods available in promises?
The primary methods are then
, catch
, and finally
.
What is promise chaining?
Promise chaining allows multiple then calls to be made in sequence, where each then runs after the previous promise completes.
What is Promise.all?
Promise.all runs multiple promises concurrently and resolves when all promises complete.
Nested Promises: Managing Complex Asynchronous Logic
At times, you may need to orchestrate multiple asynchronous operations that depend on each other.
Nested promises can help manage complex async logic.
function firstAsyncTask() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('First task completed');
resolve('Data from first task');
}, 1000);
});
}
function secondAsyncTask(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Second task completed with:', data);
resolve('Data from second task');
}, 1000);
});
}
firstAsyncTask()
.then((result) => {
return secondAsyncTask(result);
})
.then((result) => {
console.log('All tasks completed:', result);
})
.catch((error) => {
console.log('Error:', error);
});
In this example, the second task depends on the completion of the first task.
The nested promises allow you to chain these operations sequentially.
Using Promise.allSettled for Improved Reliability
While Promise.all resolves or rejects based on all promises, Promise.allSettled waits until all promises settle.
This means it handles cases where some promises may fail without interrupting the entire process.
const promiseA = Promise.resolve('Promise A resolved');
const promiseB = Promise.reject('Promise B rejected');
const promiseC = Promise.resolve('Promise C resolved');
Promise.allSettled([promiseA, promiseB, promiseC])
.then((results) => {
results.forEach((result) => {
if (result.status === 'fulfilled') {
console.log('Fulfilled:', result.value);
} else {
console.log('Rejected:', result.reason);
}
});
});
In this example, Promise.allSettled
captures the results of all promises regardless of whether they are fulfilled or rejected.
Applying Promises in Real-World Scenarios
Promises are often used to handle real-world tasks like fetching data from an API or reading files.
Promises streamline these operations, making code cleaner and more efficient.
const apiUrl = 'https://api.example.com/data';
function fetchDataFromApi() {
return fetch(apiUrl)
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then((data) => {
console.log('Data received:', data);
})
.catch((error) => {
console.log('Fetching error:', error);
});
}
fetchDataFromApi();
In this example, fetchDataFromApi
fetches data from an API and processes it upon a successful response or logs an error if something goes wrong.
Managing Promises with Async Functions
Async functions are syntactic sugar for promises, making asynchronous operations more readable.
An async function always returns a promise.
async function performAsyncTasks() {
try {
let task1Result = await firstAsyncTask();
let task2Result = await secondAsyncTask(task1Result);
console.log('Final result:', task2Result);
} catch (error) {
console.log('Error:', error);
}
}
performAsyncTasks();
In this example, performAsyncTasks
uses the await
keyword to pause execution until promises are resolved or rejected.
Using Finally to Execute Code After Promise Settles
The finally
method runs code after a promise is settled, regardless of its resolution or rejection.
This is useful for cleaning up or performing actions that should occur at the end of a task.
myPromise
.then((message) => {
console.log(message);
})
.catch((message) => {
console.log(message);
})
.finally(() => {
console.log('Task completed');
});
In this code, the finally
method executes after the promise is either resolved or rejected.
Parallel vs. Sequential Promises
Understanding when to use parallel and sequential promises is crucial for optimal performance.
Parallel promises run concurrently, while sequential promises run one after another.
// Parallel promises
Promise.all([fetchData(), processData(), saveData()])
.then(([fetchResult, processResult, saveResult]) => {
console.log(fetchResult, processResult, saveResult);
})
.catch((error) => {
console.log('Error:', error);
});
// Sequential promises
fetchData()
.then((data) => processData(data))
.then((processedData) => saveData(processedData))
.catch((error) => {
console.log('Error:', error);
});
In the parallel example, fetchData
, processData
, and saveData
run concurrently.
In the sequential example, they run one after the other.
Common Mistakes and Best Practices
Using nested callbacks:
While nesting callbacks is tempting, this can lead to callback hell.
Always prefer promise chaining for cleaner code.
Not handling rejections:
Promises can fail. Always use catch
to handle errors.
myPromise.catch((error) => {
console.error('Promise rejected:', error);
});
Forgetting to return a promise:
If you forget to return a promise from a then
callback, the chain continues with an undefined value.
myPromise
.then((data) => {
return anotherAsyncTask(data);
})
.then((result) => {
console.log(result);
});
Frequently Asked Questions
Can a promise be in more than one state?
No. A promise can only be in one of three states: pending, resolved (fulfilled), or rejected.
What happens if you don’t handle a rejected promise?
If a promise is rejected and there is no catch
block, the rejection is considered unhandled, which can lead to uncaught promise rejections.
Are promises synchronous or asynchronous?
Promises are asynchronous. They allow you to handle async tasks in a more manageable way.
What is the difference between Promise.all
and Promise.allSettled
?
Promise.all
resolves if all promises resolve, while Promise.allSettled
resolves when all promises settle, regardless of their outcome.
How do promises work with APIs like Fetch?
The fetch API returns a promise. You can chain then
and catch
methods to handle the response and errors.
Can I mix callbacks and promises?
Yes. However, it’s best to choose one approach to avoid confusion and maintain cleaner code.
What is the difference between then
and catch
?
Then
handles resolved promises. Catch
handles rejected promises.
Table of Contents
- Understanding JavaScript Promises: An Introduction
- Creating JavaScript Promises
- Consuming JavaScript Promises: Then and Catch
- Chaining Promises for Sequential Execution
- Promise.all for Concurrent Execution
- Promise.race for Competitive Execution
- Error Handling in Promises
- Combining Async/Await with Promises
- Advantages and Disadvantages of Using Promises
- Common Issues and Solutions
- Frequently Asked Questions
- Nested Promises: Managing Complex Asynchronous Logic
- Using Promise.allSettled for Improved Reliability
- Applying Promises in Real-World Scenarios
- Managing Promises with Async Functions
- Using Finally to Execute Code After Promise Settles
- Parallel vs. Sequential Promises
- Common Mistakes and Best Practices
- Frequently Asked Questions