1.0.7 • Published 8 months ago

@henrygd/queue v1.0.7

Weekly downloads
-
License
MIT
Repository
github
Last release
8 months ago

@henrygd/queue

File Size MIT license JSR Score 100%

Tiny async queue with concurrency control. Like p-limit or fastq, but smaller and faster. See comparisons and benchmarks below.

Works with:

Usage

Create a queue with the newQueue function. Then add async functions - or promise returning functions - to your queue with the add method.

You can use queue.done() to wait for the queue to be empty.

import { newQueue } from '@henrygd/queue'

// create a new queue with a concurrency of 2
const queue = newQueue(2)

const pokemon = ['ditto', 'hitmonlee', 'pidgeot', 'poliwhirl', 'golem', 'charizard']

for (const name of pokemon) {
    queue.add(async () => {
        const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`)
        const json = await res.json()
        console.log(`${json.name}: ${json.height * 10}cm | ${json.weight / 10}kg`)
    })
}

console.log('running')
await queue.done()
console.log('done')

The return value of queue.add is the same as the return value of the supplied function.

const response = await queue.add(() => fetch('https://pokeapi.co/api/v2/pokemon'))
console.log(response.ok, response.status, response.headers)

!TIP If you need support for Node's AsyncLocalStorage, import @henrygd/queue/async-storage instead.

Queue interface

/** Add an async function / promise wrapper to the queue */
queue.add<T>(promiseFunction: () => PromiseLike<T>): Promise<T>
/** Returns a promise that resolves when the queue is empty */
queue.done(): Promise<void>
/** Empties the queue (active promises are not cancelled) */
queue.clear(): void
/** Returns the number of promises currently running */
queue.active(): number
/** Returns the total number of promises in the queue */
queue.size(): number

Comparisons and benchmarks

LibraryVersionBundle size (B)Weekly downloads
@henrygd/queue1.0.6355dozens :)
p-limit5.0.01,763118,953,973
async.queue3.2.56,87353,645,627
fastq1.17.13,05039,257,355
queue7.0.02,8404,259,101
promise-queue2.2.52,2001,092,431

Note on benchmarks

All libraries run the exact same test. Each operation measures how quickly the queue can resolve 1,000 async functions. The function just increments a counter and checks if it has reached 1,000.^benchmark

We check for completion inside the function so that promise-queue and p-limit are not penalized by having to use Promise.all (they don't provide a promise that resolves when the queue is empty).

Browser benchmark

This test was run in Chromium. Chrome and Edge are the same. Firefox and Safari are slower and closer, with @henrygd/queue just edging out promise-queue. I think both are hitting the upper limit of what those browsers will allow.

You can run or tweak for yourself here: https://jsbm.dev/TKyOdie0sbpOh

@henrygd/queue - 13,665 Ops/s. fastq - 7,661 Ops/s. promise-queue - 7,650 Ops/s. async.queue - 4,060 Ops/s. p-limit - 1,067 Ops/s. queue - 721 Ops/s

Node.js benchmarks

p-limit is very slow because it uses AsyncResource.bind on every run, which is much faster in Bun than in Node or Deno.

Ryzen 5 4500U | 8GB RAM | Node 22.3.0

@henrygd/queue - 1.9x faster than fastq. 2.03x promise-queue. 3.86x async.queue. 20x queue. 86x p-limit.

Ryzen 7 6800H | 32GB RAM | Node 22.3.0

@henrygd/queue - 1.9x faster than fastq. 2.01x promise-queue. 3.98x async.queue. 6.86x queue. 88x p-limit.

Deno benchmarks

Ryzen 5 4500U | 8GB RAM | Deno 1.44.4

@henrygd/queue - 1.9x faster than fastq. 2.01x promise-queue. 4.7x async.queue. 7x queue. 28x p-limit.

Ryzen 7 6800H | 32GB RAM | Deno 1.44.4

@henrygd/queue - 1.82x faster than fastq. 1.91x promise-queue. 3.47x async.queue. 7x queue. 26x p-limit.

Bun benchmarks

Ryzen 5 4500U | 8GB RAM | Bun 1.1.17

@henrygd/queue - 1.25x faster than promise-queue. 1.66x fastq. 2.73x async.queue. 5.44x p-limit. 12x queue.

Ryzen 7 6800H | 32GB RAM | Bun 1.1.17

@henrygd/queue - 1.17x faster than promise-queue. 1.51x fastq. 2.53x async.queue. 5.25x p-limit. 5.39x queue.

Cloudflare Workers benchmark

Uses oha to make 1,000 requests to each worker. Each request creates a queue and resolves 5,000 functions.

This was run locally using Wrangler on a Ryzen 7 6800H laptop. Wrangler uses the same workerd runtime as workers deployed to Cloudflare, so the relative difference should be accurate. Here's the repository for this benchmark.

LibraryRequests/secTotal (sec)AverageSlowest
@henrygd/queue816.10741.22530.06020.0864
promise-queue647.28091.54490.07590.1149
fastq336.70313.08770.14590.2080
async.queue198.99865.02520.24680.3544
queue85.648311.67570.57320.7629
p-limit77.743412.86280.63160.9585

Related

@henrygd/semaphore - Fastest javascript inline semaphores and mutexes using async / await.

License

MIT license

^benchmark: In reality, you may not be running so many jobs at once, and your jobs will take much longer to resolve. So performance will depend more on the jobs themselves.