Promises in JavaScript are a very powerful tool, and there are a lot of applications that we never think of in the first place. One of them is to use Promises as a queue.
Let’s imagine you want to execute a number of asynchronous operations in order, one after another. If you know the operations in advance, your code will look like this:
await operation1()
await operation2()
await operation3()
But what if you have an arbitrary number of operations to run? And if each of them can trigger more? Then you need a queuing system.
If we come back to the previous case, we could also have written it this way:
operation1()
.then(operation2)
.then(operation3)
Which translates in pseudo code to
Promise
.then(FunctionReturningPromise)
.then(FunctionReturningPromise)
Then, queuing an operation would be as easy as this:
add(operation) {
queue.then(operation)
}
Unfortunately, with this implementation, you will always execute your .then
on the same promise. We need to keep track of what is the last element:
add(operation) {
queue = queue.then(operation)
}
The code is still very wrong, because if one operation throws, the queue will stop. For instance, in this code, ‘second message’ will never appear on your screen:
Promise
.resolve()
.then(() => console.log(‘first message’))
.then(() => { throw new Error(an error) })
.then(() => console.log(‘second message’))
One way to avoid this is to add a catch statement after every .then
:
add(operation) {
queue = queue.then(operation).catch(() => {
// do whatever, log the error?
})
}
Now it’s better; we still need to initialise the queue though. A very easy way to do this is to actually generate a Promise that is already resolved:
queue = Promise.resolve()
This gives us a complete implementation here:
class PromiseQueue {
queue = Promise.resolve()
add(operation) {
this.queue = this.queue.then(operation).catch(() => {})
}
}
Drawback: with this simple implementation, you don’t get feedback on whether or not your operation succeeded in the code where you add the operation to the queue. You could also implement add
so that it returns a promise that resolves once this specific operation has resolved (so you can get the feedback). This should do the trick:
class PromiseQueue {
queue = Promise.resolve(true)
add(operation) {
return new Promise((resolve, reject) => {
this.queue = this.queue
.then(operation)
.then(resolve)
.catch(reject)
})
}
}
Top comments (3)
Nice!I spent a few minutes registering for the DEV community to like.
Nice solution! Thanks for sharing 💚
I love it!