Breaking
- Require Node.js 22 300a52d
- The queue is now an
EventTargetinstead of anEventEmitter300a52d- Use
.addEventListener()and.removeEventListener()instead of.on(),.once(),.off(), and the otherEventEmittermethods. - The
completedanderrorevents have the result or the error inevent.detail. - An error thrown in an event listener now exits the process. Node.js reports it as an uncaught exception.
- Use
.clear()now rejects the promises of the removed tasks with anAbortErrorinstead of leaving them pending forever 64311ff.clear()marks these promises as handled, but a promise made from them, like the one from.addAll()orqueue.add(fn).then(…), now rejects, so handle it.
TimeoutErroris now aDOMExceptionnamedTimeoutError, like the error ofAbortSignal.timeout()300a52d- Remove the deprecated
carryoverConcurrencyCountoption: usecarryoverIntervalCount300a52d - Stricter validation of options 76a48ba d65d567
concurrencyandintervalCapmust be integers (orInfinity). A limit of1.5used to let 2 tasks run.timeoutandintervalabove2147483647ms (the maximum timer delay) are rejected. They used to fire after 1 ms..add()rejects with aTypeErrorwhenpriorityisNaNor not a number.
.start(),.clear(), and settingconcurrencyno longer emitemptyandidlewhen the queue is already empty, soidleno longer fires on a queue that never had a task aeedf53- A custom
queueClassmust now implementremove()300a52d
Improvements
- Zero dependencies! 300a52d
- A task now runs in the async context of its
.add()call, so it sees theAsyncLocalStoragevalues of the caller, also when it starts later (Node.js 22.3 and later, Deno, and Bun) (#200) a5b7b54- Previously, a waiting task could see the values of whatever started it, like another request on a server.
- A task with a
timeoutnow gets asignalthat aborts when the task times out, so you can pass it on tofetch()and similar to stop the work 300a52d .onEmpty()and theemptyevent now fire when the last waiting task starts, not when the next task finishes 1cf5b82 cb25a0a.addAll()now resolves with a typed tuple when the tasks return different types 36e83aa
Fixes
- Fix many rate-limit bugs where
intervalCapwas exceeded at the end of a window, a waiting task got stuck, orisRateLimitedwas wrong 6af55a7 1cf5b82 6c08a4f ac89911 450356a - Fix slow add, run, and abort for many tasks that share a signal, and
PriorityQueuekeeping finished tasks in memory 95622a4 6fb8337 - Fix many edge cases with events and priorities, like infinite recursion in an
idlelistener and events in the wrong order aeedf53 b33dd44 8792846 e79f970 d65d567 6f6028a - Fix
undefinedoptions and an invalid.timeoutsetter breaking the queue d6cf96b 48586f4 - Fix many TypeScript types 36e83aa fa3c3e0
Migration guide
Events
-queue.on('completed', result => {
- console.log(result);
-});
+queue.addEventListener('completed', event => {
+ console.log(event.detail);
+});
-queue.once('idle', () => {});
+queue.addEventListener('idle', () => {}, {once: true});If the result or the error is undefined, event.detail is null, because CustomEvent works that way.
Other differences from EventEmitter:
- If you add the same listener twice, it is called once.
- A listener that is removed while an event is dispatched is not called for that event.
.clear()
The promises of the removed tasks now reject with an AbortError. If you await them, handle the rejection:
const results = await queue.addAll(tasks).catch(error => {
if (error.name === 'AbortError') {
return [];
}
throw error;
});Timeouts and signals
error instanceof TimeoutError still works. You can also check error.name === 'TimeoutError', which also matches the error of AbortSignal.timeout().
Other changes:
- The
TimeoutErrorstack trace no longer shows where the task was added. - With
timeoutset, the task'ssignalis no longer linked to yoursignalafter the task finishes. - Tasks with a
signalortimeoutsettle 2 microtasks later than before.