Implementing a Priority Queue for Promises in JavaScript
Imagine a scenario where you have a list of promises, and you want to get the result of the first resolved promise. If the first promise is rejected, you'd like to ensure that the function returns the result of the next resolved promise. And, in the worst-case scenario where all promises are rejected, you want to gather a list of errors. In this blog post, we'll discuss how to implement a priority queue for promises in JavaScript to achieve this.
The Problem
Consider the following example:
const resolveIn = (time) => new Promise((resolve) => setTimeout(() => resolve(time), time));
const rejectIn = (time) => new Promise((_, reject) => setTimeout(() => reject(time), time));
const firstResolved = async (promises) => {
// Your solution goes here
};
const promises1 = [rejectIn(1000), resolveIn(500), resolveIn(20000)];
const result1 = await firstResolved(promises1);
console.log(result1); // 500 (after 1000ms)
const promises2 = [rejectIn(1000), resolveIn(5000), resolveIn(100)];
const result2 = await firstResolved(promises2);
console.log(result2); // 5000 (after 5000ms)
In this example, we have an array of promises. The firstResolved
function should return the first resolved promise, and if the first promise is rejected, it should return the next resolved promise. If all promises are rejected, it should return a list of errors.
Solution Overview
To implement this behavior, we need to execute all promises in parallel, similar to running a race. We cannot use Promise.all
because it would reject the entire operation if any promise is rejected. Also, we don't want to wait for the last promise to resolve; we want to return the result as soon as possible. The same issue applies to Promise.allSettled
.
Promise.any
is a good candidate for this operation. It returns the first resolved promise but in the second example, it returns the result of the last promise because it's the first resolved promise. This is not the behavior we want.
Let's implement a custom solution to handle this priority queue for promises.
The Implementation
const firstResolved = (promises) => {
if (!promises || !promises.length)
return Promise.reject("No promises provided");
const results = Array(promises.length).fill(null);
return new Promise((resolve, reject) => {
const checkResults = () => {
for (let i = 0; i < results.length; i++) {
if (results[i] === null) return;
if (results[i].status === "resolved") return resolve(results[i].value);
}
reject(results);
};
promises.forEach((promise, i) => {
Promise.resolve(promise).then((result) => {
results[i] = { status: "resolved", value: result };
}).catch((error) => {
results[i] = { status: "rejected", reason: error };
}).finally(checkResults);
});
});
};
Here's how the solution works:
We first check if any promises are provided. If not, we reject the operation with an appropriate message.
We create an array called
results
with the same length as the number of promises. This array will store the status and value of each promise.We return a new promise that handles the resolution and rejection logic.
The
checkResults
function is responsible for checking the status of the promises. It looks through theresults
array and immediately resolves the promise when it finds the first resolved promise. If all promises are rejected, it rejects the operation with the collected error information.We iterate through the promises using a
forEach
loop. For each promise, we attachthen
,catch
, andfinally
handlers. Thethen
handler updates theresults
array with the resolved value, thecatch
handler updates it with the rejection reason, and thefinally
handler callscheckResults
.
With this implementation, the function will return the result of the first resolved promise or a list of errors if all promises are rejected.
Testing the Implementation
Let's test the firstResolved
function with the examples we discussed earlier.
const promises1 = [rejectIn(1000), resolveIn(500), resolveIn(20000)];
const result1 = await firstResolved(promises1);
console.log(result1); // 500 (after 1000ms)
const promises2 = [rejectIn(1000), resolveIn(5000), resolveIn(100)];
const result2 = await firstResolved(promises2);
console.log(result2); // 5000 (after 5000ms)
Thanks for reading! I hope you enjoyed this article. Feel free to share your thoughts in the comments below.
Must Read If you haven't
3 steps to create custom state management library with React and Context API
How to cancel Javascript API request with AbortController
Getting started with SolidJs β A Beginner's Guide
More content at Dev.to.
Catch me on
Youtube Github LinkedIn Medium Stackblitz Hashnode HackerNoon
Top comments (0)