Everything You Need to Know About Promise, all in JavaScript
JavaScript is fundamentally an asynchronous language. This means it can handle operations that take time, like fetching data from a server, reading files, or waiting for user input, without blocking the main thread. Asynchronous programming is crucial because it allows JavaScript to remain responsive and efficient, especially in web browsers or servers. Traditionally, handling asynchronous operations in JavaScript involved callbacks, which could lead to complicated and hard-to-maintain code known as “callback hell.” To solve this, JavaScript introduced promises, which provide a cleaner and more manageable way to work with asynchronous tasks.
A promise is an object representing the eventual completion or failure of an asynchronous operation and its resulting value. It acts as a placeholder for a value that will be available in the future. A promise can be in one of three states:
While promises simplify asynchronous code, developers often face situations where multiple asynchronous tasks need to run concurrently. For example, you might need to fetch data from several different endpoints or perform multiple file operations simultaneously. In these cases, you want to wait until all these tasks are completed before moving forward. This is where Promise. All becomes invaluable. It allows you to run multiple promises in parallel and wait until all of them either resolve or reject. It aggregates the results into a single promise, making it easier to coordinate multiple asynchronous operations.
Promise. all is a method provided by the Promise API that takes an iterable (usually an array) of promises and returns a single promise. This returned promise settles based on the state of the promises it is given:
javascript
CopyEdit
PromiseAll(iterable);
To grasp the power of PromiseAll, it’s important to understand the mechanics behind its operation. Here are the essential principles:
PromiseAll waits for all promises in the input iterable to settle. Only when all promises resolve successfully does it resolve with an array of results. This is useful when you need to ensure that every asynchronous task completes before moving on. If even one promise is rejected, PromiseAll rejects immediately with that rejection reason. It doesn’t wait for the remaining promises to settle once it has encountered a rejection.
Even though promises might resolve at different times, the array returned by PromiseAll always preserves the order of the original promises. This means you can rely on the position of each result matching the position of its corresponding promise in the input iterable.
If the iterable contains non-promise values, PromiseAll treats these as resolved promises with those values. This allows a mix of promises and plain values in the same input array without issues.
When one promise is rejected, the returned promise rejects right away with the error from that promise. Any other promises that are still pending continue to execute, but their results are ignored by PromiseAll.
To understand PromiseAll better, let’s explore practical examples that demonstrate its core functionalities.
javascript
CopyEdit
const promise1 = new Promise(resolve => setTimeout(() => resolve(‘One’), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve(‘Two’), 2000));
const promise3 = new Promise(resolve => setTimeout(() => resolve(‘Three’), 3000));
PromiseAll([promise1, promise2, promise3])
.then(values => console.log(values))
.catch(error => console.error(error));
In this example, three promises resolve after different durations. PromiseAll waits for all of them to finish and then logs an array of their results: [‘One’, ‘Two’, ‘Three’]. If any of these promises were to be rejected, the catch block would run instead.
javascript
CopyEdit
const promise1 = Promise.resolve(1);
const promise2 = ‘Not a promise’;
const promise3 = new Promise(resolve => setTimeout(() => resolve(‘Three’), 1000));
PromiseAll([promise1, promise2, promise3])
.then(values => console.log(values))
.catch(error => console.error(error));
Here, promise2 is not a promise but a plain value. PromiseAll treats it as a resolved promise with the value ‘Not a promise’. The output will be [1, ‘Not a promise’, ‘Three’] once all promises resolve.
javascript
CopyEdit
const promise1 = new Promise(resolve => setTimeout(() => resolve(‘Success’), 1000));
const promise2 = new Promise((_, reject) => setTimeout(() => reject(‘Failure’), 500));
const promise3 = new Promise(resolve => setTimeout(() => resolve(‘Success too’), 1500));
PromiseAll([promise1, promise2, promise3])
.then(values => console.log(values))
.catch(error => console.error(error));
In this example, promise2 rejects after 500 milliseconds. PromiseAll immediately rejects with the error ‘Failure’. The other promises continue to run, but their results are ignored.
Using PromiseAll is appropriate when you have multiple asynchronous tasks that:
Although PromiseAll is very powerful, it’s essential to understand its limitations:
Building upon the fundamentals of PromiseAll discussed earlier, this section explores more sophisticated use cases, focusing on error handling, integrating non-promise values, comparison with other Promise utility methods, and best practices to maximize its potential in real-world applications.
One of the most important aspects of working with PromiseAll is understanding how to manage promise rejections and errors effectively. Since PromiseAll rejects as soon as any promise rejects, this behavior can simplify error handling, but can also introduce challenges when you want to continue processing despite individual errors.
The simplest way to handle errors in PromiseAll is to attach a .catch() method to the returned promise. This catch block will be triggered if any promise in the iterable rejects.
javascript
CopyEdit
const promise1 = Promise.resolve(‘Success 1’);
const promise2 = Promise.reject(‘Failure in promise2’);
const promise3 = Promise.resolve(‘Success 3’);
PromiseAll([promise1, promise2, promise3])
.then(results => {
console.log(‘All succeeded:’, results);
})
.catch(error => {
console.error(‘One promise failed:’, error);
});
In this example, the error ‘Failure in promise2’ is caught immediately, and the other promises are ignored in terms of result handling, even if they eventually resolve.
If you want to wait for all promises to complete regardless of individual rejections—so you can handle errors on a per-promise basis—PromiseAll by itself is not sufficient because it rejects on the first failure. In this case, you can wrap each promise in a way that they never reject but instead always resolve to an object indicating success or failure.
javascript
CopyEdit
const reflect = (promise) =>
promise.then(
value => ({ status: ‘fulfilled’, value }),
error => ({ status: ‘rejected’, reason: error })
);
const promise1 = Promise.resolve(‘Success 1’);
const promise2 = Promise.reject(‘Failure in promise2’);
const promise3 = Promise.resolve(‘Success 3’);
PromiseAll([reflect(promise1), reflect(promise2), reflect(promise3)])
.then(results => {
results.forEach(result => {
if (result.status === ‘fulfilled’) {
console.log(‘Success:’, result.value);
} else {
console.error(‘Error:’, result.reason);
}
});
});
This pattern is very useful when you want to handle the success and failure of promises individually without stopping the overall process due to a single error.
Introduced in ECMAScript 2020, PromiseAllSettled waits for all promises to settle, regardless of whether they fulfill or reject. It returns an array of objects describing each promise’s outcome.
javascript
CopyEdit
const promise1 = Promise.resolve(‘Success 1’);
const promise2 = Promise.reject(‘Failure in promise2’);
const promise3 = Promise.resolve(‘Success 3’);
PromiseAllSettled([promise1, promise2, promise3])
.then(results => {
results.forEach(result => {
if (result.status === ‘fulfilled’) {
console.log(‘Success:’, result.value);
} else {
console.error(‘Failure:’, result.reason);
}
});
});
This method eliminates the need for wrapping promises manually and is recommended when you want a full report of all promise results, whether successful or failed.
As seen in earlier examples, PromiseAll treats non-promise values as resolved promises. This flexibility allows combining synchronous and asynchronous values in the same iterable.
javascript
CopyEdit
const promise1 = Promise.resolve(‘Promise resolved’);
const normalValue = 42;
const promise2 = new Promise(resolve => setTimeout(() => resolve(‘Delayed promise’), 500));
PromiseAll([promise1, normalValue, promise2])
.then(values => {
console.log(values); // Output: [‘Promise resolved’, 42, ‘Delayed promise’]
});
This behavior is useful when you have a mixture of data and asynchronous operations and want a single unified way to handle the result.
Understanding how PromiseAll compares with other Promise combinators, such as Promise. Race, Promise. Any and PromiseAllSettled are essential for choosing the right tool based on your needs.
Example:
javascript
CopyEdit
const fastReject = new Promise((_, reject) => setTimeout(() => reject(‘Rejected fast’), 100));
const slowResolve = new Promise(resolve => setTimeout(() => resolve(‘Resolved slow’), 500));
Promise.race([fastReject, slowResolve])
.then(value => console.log(‘Race resolved with:’, value))
.catch(error => console.error(‘Race rejected with:’, error));
In this example, Promise. Race rejects quickly with ‘Rejected fast’, unlike PromiseAll, which would wait for all promises or the first rejection.
Promise. Any resolves as soon as any of the input promises resolve successfully. It rejects only if all promises are rejected.
javascript
CopyEdit
const p1 = Promise.reject(‘Error 1’);
const p2 = Promise.reject(‘Error 2’);
const p3 = Promise.resolve(‘Success’);
Promise.any([p1, p2, p3])
.then(value => console.log(‘Any resolved with:’, value))
.catch(error => console.error(‘Any rejected with:’, error));
Here, Promise. Any resolves immediately with ‘Success’ and ignores rejected promises.
While PromiseAll rejects on the first rejection, PromiseAllSettled waits for all promises to settle and returns an array with status and value, or reason for each promise.
Use PromiseAll when you have multiple independent tasks that can run in parallel and you need all results before continuing. This improves efficiency compared to waiting for each promise sequentially.
Since PromiseAll guarantees the order of results matches the input order, it is suitable when you require consistent ordering regardless of individual promise completion times.
Since any rejection causes immediate failure, wrap individual promises if you want partial success rather than total failure, or consider using PromiseAllSettled.
If one promise takes significantly longer than others, it delays the entire PromiseAll. Consider splitting such promises into separate groups or applying timeouts.
JavaScript promises do not support native timeout, but you can implement timeout behavior by combining Promise. Race with a timeout promise.
javascript
CopyEdit
function timeoutPromise(ms) {
return new Promise((_, reject) => setTimeout(() => reject(‘Timeout’), ms));
}
const slowPromise = new Promise(resolve => setTimeout(() => resolve(‘Slow success’), 2000));
const fastPromise = Promise.resolve(‘Fast success’);
Promise.race([PromiseAll([slowPromise, fastPromise]), timeoutPromise(1000)])
.then(results => {
console.log(‘Results:’, results);
})
.catch(error => {
console.error(‘Error or timeout:’, error);
});
In this example, the timeout causes rejection if the promises don’t settle within 1 second.
The introduction of async/await syntax simplifies working with promises and promises.
javascript
CopyEdit
async function fetchAll() {
const promise1 = fetch(‘https://api.example.com/data1’);
const promise2 = fetch(‘https://api.example.com/data2’);
const promise3 = fetch(‘https://api.example.com/data3’);
try {
const responses = await PromiseAll([promise1, promise2, promise3]);
const data = await PromiseAll(responses.map(response => response.json()));
console.log(data);
} catch (error) {
console.error(‘Failed to fetch:’, error);
}
}
fetchAll();
Here, PromiseAll is used to concurrently fetch data from multiple endpoints, with await simplifying promise resolution and error handling.
Using PromiseAll helps improve performance by parallelizing tasks, reducing overall wait times compared to sequential awaits. However, be mindful of resource constraints such as API rate limits or system capacity when firing many concurrent promises.
Promises can be tricky to debug due to their asynchronous nature. When working with PromiseAll, ensure you:
Understanding PromiseAll deeply requires not only knowing how it works but also how to apply it effectively in real-world scenarios. This section explores practical use cases where PromiseAll shines, as well as some pitfalls and solutions.
A common use case is fetching data from multiple APIs simultaneously, which improves performance by reducing total waiting time.
javascript
CopyEdit
async function fetchUserData() {
const userPromise = fetch(‘/api/user’);
const postsPromise = fetch(‘/api/posts’);
const commentsPromise = fetch(‘/api/comments’);
try {
const [userRes, postsRes, commentsRes] = await PromiseAll([userPromise, postsPromise, commentsPromise]);
const user = await userRes.json();
const posts = await postsRes.json();
const comments = await commentsRes.json();
return { user, posts, comments };
} catch (error) {
console.error(‘Failed to fetch one or more resources:’, error);
throw error;
}
}
fetchUserData().then(data => console.log(data));
This approach fetches all resources concurrently rather than sequentially, minimizing total latency.
When querying a database for multiple independent pieces of data, running queries in parallel using PromiseAll speeds up response time.
javascript
CopyEdit
function getUser(userId) {
return db.query(‘SELECT * FROM users WHERE id = ?’, [userId]);
}
function getOrders(userId) {
return db.query(‘SELECT * FROM orders WHERE user_id = ?’, [userId]);
}
function getWishlist(userId) {
return db.query(‘SELECT * FROM wishlist WHERE user_id = ?’, [userId]);
}
async function fetchUserDashboardData(userId) {
try {
const [user, orders, wishlist] = await PromiseAll([
getUser(userId),
getOrders(userId),
getWishlist(userId)
]);
return { user, orders, wishlist };
} catch (error) {
console.error(‘Database query failed:’, error);
throw error;
}
}
Running these queries concurrently reduces the time needed to load a user’s dashboard significantly.
In Node.js environments, performing multiple file operations at once can leverage asynchronous I/O.
javascript
CopyEdit
const fs = require(‘fs/promises’);
async function readFiles(filePaths) {
try {
const fileReadPromises = filePaths.map(path => fs.readFile(path, ‘utf-8’));
const contents = await PromiseAll(fileReadPromises);
return contents;
} catch (error) {
console.error(‘Error reading files:’, error);
throw error;
}
}
This example reads several files concurrently, speeding up batch processing of files.
While PromiseAll is powerful, developers often encounter issues or misuse it in ways that lead to bugs or inefficiencies.
Since PromiseAll rejects immediately upon any promise failure, this can lead to a loss of partial results from other promises that may have resolved successfully.
If partial data is valuable, wrapping promises individually (as shown earlier with a reflect helper) or using PromiseAllSettled is preferable.
Omitting error handling can cause unhandled promise rejection warnings or silent failures.
Always chain. Catch () or use try/catch with async/await to handle errors gracefully.
Firing a huge number of promises simultaneously may overwhelm system resources, network bandwidth, or API rate limits.
Consider batching promises in smaller groups or implementing concurrency limits using libraries like p-limit.
Example with batching:
javascript
CopyEdit
async function batchPromises(items, batchSize, fn) {
let results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await PromiseAll(batch.map(fn));
results = results.concat(batchResults);
}
return results;
}
This function processes items in batches to avoid excessive concurrency.
Even though PromiseAll accepts non-promise values, mixing sync and async logic without understanding execution order can cause subtle bugs.
Always be clear on which parts are asynchronous and which are synchronous to avoid surprises.
Async/await provides cleaner syntax to write asynchronous code that reads like synchronous code. However, awaiting promises sequentially negates the benefit of concurrency. Combining PromiseAll with async/await allows parallel execution with clean syntax.
Example without PromiseAll (sequential):
javascript
CopyEdit
async function sequential() {
const result1 = await task1();
const result2 = await task2();
const result3 = await task3();
return [result1, result2, result3];
}
Here, each task waits for the previous one to finish, increasing the total time.
Example with PromiseAll (parallel):
javascript
CopyEdit
async function parallel() {
const [result1, result2, result3] = await PromiseAll([task1(), task2(), task3()]);
return [result1, result2, result3];
}
This runs tasks concurrently, reducing total time to the longest single task.
Use try/catch around await PromiseAll() to catch any rejected promise:
javascript
CopyEdit
async function loadData() {
try {
const results = await PromiseAll([fetchData1(), fetchData2()]);
console.log(results);
} catch (error) {
console.error(‘One or more promises failed:’, error);
}
}
If any promise is rejected, the catch block executes immediately with the rejection reason.
A common anti-pattern is awaiting promises inside loops, causing sequential execution.
javascript
CopyEdit
// Bad: sequential
for (const item of items) {
await asyncTask(item);
}
Instead, collect all promises first, then await them together:
javascript
CopyEdit
// Good: parallel
const promises = items.map(item => asyncTask(item));
await PromiseAll(promises);
This approach maximizes concurrency and performance.
When a promise rejects, PromiseAll rejects immediately with that error, and no further results are processed.
Consider the implications if multiple promises are rejected nearly simultaneously: only the first rejection is reported.
Sometimes you want the whole batch to continue even if some promises fail. To do so, wrap promises so they never reject:
javascript
CopyEdit
const safePromise = (p) => p.catch(error => ({ error }));
const promises = [
safePromise(promise1),
safePromise(promise2),
safePromise(promise3),
];
const results = await PromiseAll(promises);
results.forEach(result => {
if (result.error) {
console.error(‘Handled error:’, result.error);
} else {
console.log(‘Result:’, result);
}
});
This approach prevents PromiseAll from rejecting, allowing handling of all results.
PromiseAllSettled gives full insight into the success or failure of every promise without stopping at the first rejection.
javascript
CopyEdit
const results = await PromiseAllSettled([promise1, promise2, promise3]);
results.forEach(result => {
if (result.status === ‘fulfilled’) {
console.log(‘Success:’, result.value);
} else {
console.error(‘Failure:’, result.reason);
}
});
Suppose a web app allows users to upload multiple images at once. You want to upload all images in parallel and show results when all are finished
javascript
CopyEdit
async function uploadImages(imageFiles) {
try {
const uploadPromises = imageFiles.map(file => uploadFileToServer(file));
const results = await PromiseAll(uploadPromises);
console.log(‘All uploads complete:’, results);
} catch (error) {
console.error(‘One or more uploads failed:’, error);
}
}
If any upload fails, the catch block executes immediately, and the process stops.
To handle partial failures:
javascript
CopyEdit
async function uploadImagesSafe(imageFiles) {
const safeUpload = file => uploadFileToServer(file).catch(error => ({ error }));
const uploadPromises = imageFiles.map(safeUpload);
const results = await PromiseAll(uploadPromises);
results.forEach(result => {
if (result.error) {
console.error(‘Upload failed:’, result.error);
} else {
console.log(‘Upload succeeded:’, result);
}
});
}
This reports on all uploads regardless of success or failure.
When dealing with large datasets or numerous asynchronous operations, firing hundreds or thousands of promises simultaneously using PromiseAll can overwhelm system resources, cause rate-limiting issues, or degrade performance. To handle this, concurrency control techniques are essential.
One straightforward way is to batch promises into smaller groups, processing each batch sequentially but running promises in the batch concurrently.
javascript
CopyEdit
async function processInBatches(items, batchSize, asyncFn) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await PromiseAll(batch.map(asyncFn));
results.push(…batchResults);
}
return results;
}
This approach balances between concurrency and resource limits by controlling the number of simultaneous promises.
Libraries like p-limit or bluebird offer utilities for limiting concurrency elegantly.
Example with p-limit:
javascript
CopyEdit
import pLimit from ‘p-limit’;
const limit = pLimit(5); // Maximum 5 concurrent promises
const limitedPromises = items.map(item => limit(() => asyncTask(item)));
const results = await PromiseAll(limitedPromises);
This pattern allows fine-grained control over concurrency without manual batching.
JavaScript’s asynchronous ecosystem includes streams and event emitters, which often produce data over time instead of immediately returning promises. PromiseAll can be combined effectively with these abstractions.
Suppose you want to wait for multiple streams to finish reading data before processing collectively.
javascript
CopyEdit
function streamToPromise(stream) {
return new Promise((resolve, reject) => {
const chunks = [];
stream.on(‘data’, chunk => chunks.push(chunk));
stream.on(‘end’, () => resolve(Buffer.concat(chunks)));
stream.on(‘error’, reject);
});
}
async function processStreams(streams) {
const promises = streams.map(streamToPromise);
const buffers = await PromiseAll(promises);
// Process all buffers together
}
Here, PromiseAll aggregates the completion of multiple streams.
Testing asynchronous code that uses PromiseAll requires care to cover success, failure, and partial failure scenarios.
Mock asynchronous functions to return resolved promises and verify the combined result.
javascript
CopyEdit
test(‘processData resolves all tasks’, async () => {
const mockFn = jest.fn().mockResolvedValue(‘success’);
const results = await PromiseAll([mockFn(), mockFn(), mockFn()]);
expect(results).toEqual([‘success’, ‘success’, ‘success’]);
});
Verify that your code correctly catches the first rejection from PromiseAll.
javascript
CopyEdit
test(‘processData rejects on first failure’, async () => {
const success = jest.fn().mockResolvedValue(‘ok’);
const fail = jest.fn().mockRejectedValue(new Error(‘fail’));
await expect(PromiseAll([success(), fail(), success()])).rejects.toThrow(‘fail’);
});
If your code wraps promises to prevent rejection and instead returns error objects, test that the results contain both success and failure cases.
PromiseAll returns results in the order of the iterable passed to it, not in the order promises settle.
Example:
javascript
CopyEdit
const p1 = new Promise(res => setTimeout(() => res(‘first’), 300));
const p2 = new Promise(res => setTimeout(() => res(‘second’), 100));
const p3 = new Promise(res => setTimeout(() => res(‘third’), 200));
PromiseAll([p1, p2, p3]).then(console.log);
// Output: [‘first’, ‘second’, ‘third’] because order corresponds to the input array, not completion time.
PromiseAll rejects immediately once a promise rejects, but other promises continue executing. The rejection just short-circuits the outer promise
Promise. Race settles as soon as one promise settles, whether fulfilled or rejected.
Use cases: timeouts, first-available responses.
javascript
CopyEdit
Promise.race([
fetch(url),
new Promise((_, reject) => setTimeout(() => reject(‘timeout’), 5000))
])
.then(response => console.log(‘Received response’))
.catch(error => console.error(error));
Returns an array of outcome objects for all promises, never rejects.
Use case: want to wait for all promises regardless of rejection.
javascript
CopyEdit
const results = await PromiseAllSettled([p1, p2, p3]);
results.forEach(result => {
if (result.status === ‘fulfilled’) {
console.log(‘Success:’, result.value);
} else {
console.log(‘Failed:’, result.reason);
}
});
Resolves when any promise iis fulfilled rejects if all are rejected.
Useful when you want the first successful result but don’t want to fail on initial rejections.
PromiseAll takes an iterable of promises and returns a new promise. It tracks each input promise’s resolution or rejection. Internally, it creates a results array and a counter for settled promises. Once all promises are fulfilled, it resolves with the results array. If any reject, it rejects immediately with that reason.
This behavior allows PromiseAll to be both powerful and predictable, but understanding this helps in troubleshooting complex asynchronous flows.
Since PromiseAll rejects on the first failure, always attach a .catch() or use try/catch with async/await.
Collect all promises before awaiting them to leverage concurrency.
Batch promises or use libraries for concurrency limits to avoid resource exhaustion.
This avoids unhandled rejections and provides detailed outcomes for each promise.
Explicitly name promises and document intent when passing many promises to PromiseAll.
PromiseAll is a cornerstone for asynchronous JavaScript programming, enabling the parallel execution of multiple promises with simple syntax. It is essential for performance optimization and clean code structure when handling multiple asynchronous tasks.
Mastering its nuances—error handling, concurrency control, and interplay with async/await—helps developers build robust, efficient, and maintainable applications. Pairing it thoughtfully with complementary APIs like PromiseAllSettled or libraries for concurrency can elevate asynchronous JavaScript to professional-grade quality.
Popular posts
Recent Posts