henrygd / queue

Tiny async queue with concurrency control. Like p-limit or fastq, but smaller and faster
MIT License
51 stars 0 forks source link

Does the queue respect order in which tasks are added? #1

Closed retorquere closed 3 months ago

retorquere commented 3 months ago

When I run this:

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']

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

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

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

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

I get out of order output:

running
0: ditto: 30cm | 4kg
1: hitmonlee: 150cm | 49.8kg
3: poliwhirl: 100cm | 20kg
2: pidgeot: 150cm | 39.5kg
4: golem: 140cm | 300kg
5: charizard: 170cm | 90.5kg
done
running again
7: hitmonlee (again): 150cm | 49.8kg
6: ditto (again): 30cm | 4kg
8: pidgeot (again): 150cm | 39.5kg
9: poliwhirl (again): 100cm | 20kg
10: golem (again): 140cm | 300kg
11: charizard (again): 170cm | 90.5kg
retorquere commented 3 months ago

Doesn't look like it:

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']

function randomWait() {
    return new Promise(function(resolve) {
        setTimeout(resolve, Math.floor(Math.random() * 5000))
    })
}

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

gives me

0: getting ditto
1: getting hitmonlee
0: ditto: 30cm | 4kg
2: getting pidgeot
1: hitmonlee: 150cm | 49.8kg
3: getting poliwhirl
3: poliwhirl: 100cm | 20kg
4: getting golem
2: pidgeot: 150cm | 39.5kg
5: getting charizard
4: golem: 140cm | 300kg
5: charizard: 170cm | 90.5kg
retorquere commented 3 months ago

Argh concurrency sorry!

henrygd commented 3 months ago

You can use Promise.all or Promise.allSettled to maintain order.

import { newQueue } from '@henrygd/queue'

const queue = newQueue(5)

const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

const randomWait = () => new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 500)))

const jobs = []

for (const num of nums) {
    jobs.push(queue.add(async () => {
        await randomWait()
        return num
    }))
}

const results = await Promise.all(jobs)

for (const result of results) {
    console.log(result)
}