UNPKG

concurrent-promise-queue

Version:

Allows promises to be queued up and executed at a maximum rate defined by time or max concurrency

289 lines (226 loc) 9.71 kB
import {ConcurrentPromiseQueue} from "./index"; interface Ticket { ticketNumber:number, timestamp:Date, } class TicketGenerator { ticketNumber:number = 0 generate():Ticket { return { ticketNumber: this.ticketNumber++, timestamp: new Date(), } } } const sleep = async (millis:number) => { return new Promise(resolve => setTimeout(resolve, millis)) } describe("concurrent promise queue", () => { it("Should execute Promises in sequence", async () => { const queue = new ConcurrentPromiseQueue({maxNumberOfConcurrentPromises: 1}) const ticketGenerator = new TicketGenerator() const generatePromise = (expectedTicketNumbers:Array<number>) => { const start = new Date() return async () => { await sleep(100) const ticket = ticketGenerator.generate() const millisSinceStart = ticket.timestamp.getTime() - start.getTime() expect(expectedTicketNumbers).toContain(ticket.ticketNumber) } } await Promise.all([ queue.addPromise(generatePromise([0])), queue.addPromise(generatePromise([1])), queue.addPromise(generatePromise([2])), queue.addPromise(generatePromise([3])), queue.addPromise(generatePromise([4])), ]) }) it("Should execute Promises in pairs", async () => { const queue = new ConcurrentPromiseQueue({maxNumberOfConcurrentPromises: 2}) const ticketGenerator = new TicketGenerator() const generatePromise = (expectedTicketNumbers:Array<number>) => { const start = new Date() return async () => { await sleep(100) const ticket = ticketGenerator.generate() const millisSinceStart = ticket.timestamp.getTime() - start.getTime() expect(expectedTicketNumbers).toContain(ticket.ticketNumber) } } await Promise.all([ queue.addPromise(generatePromise([0, 1])), queue.addPromise(generatePromise([0, 1])), queue.addPromise(generatePromise([2, 3])), queue.addPromise(generatePromise([2, 3])), queue.addPromise(generatePromise([4, 5])), queue.addPromise(generatePromise([4, 5])), ]) }) it("Should execute Promises in triplets", async () => { const queue = new ConcurrentPromiseQueue({maxNumberOfConcurrentPromises: 3}) const ticketGenerator = new TicketGenerator() const generatePromise = (expectedTicketNumbers:Array<number>) => { const start = new Date() return async () => { await sleep(100) const ticket = ticketGenerator.generate() const millisSinceStart = ticket.timestamp.getTime() - start.getTime() expect(expectedTicketNumbers).toContain(ticket.ticketNumber) } } await Promise.all([ queue.addPromise(generatePromise([0, 1, 2])), queue.addPromise(generatePromise([0, 1, 2])), queue.addPromise(generatePromise([0, 1, 2])), queue.addPromise(generatePromise([3, 4, 5])), queue.addPromise(generatePromise([3, 4, 5])), queue.addPromise(generatePromise([3, 4, 5])), queue.addPromise(generatePromise([6, 7, 8])), queue.addPromise(generatePromise([6, 7, 8])), queue.addPromise(generatePromise([6, 7, 8])), ]) }) it("Should execute Promises at max 1 per 500ms", async () => { const queue = new ConcurrentPromiseQueue({ unitOfTimeMillis: 500, maxThroughputPerUnitTime: 1, }) const ticketGenerator = new TicketGenerator() const generatePromise = () => { const start = new Date() return async () => { await sleep(100) const ticket = ticketGenerator.generate() const millisSinceStart = ticket.timestamp.getTime() - start.getTime() return millisSinceStart } } const millisRunAt:Array<number|null> = await Promise.all([ queue.addPromise(generatePromise()), queue.addPromise(generatePromise()), queue.addPromise(generatePromise()), queue.addPromise(generatePromise()), ]) if (millisRunAt.some(milli => milli === null)) { fail(`Some millis were null: ${millisRunAt.join(", ")}`) } const milli1 = <number>millisRunAt[0] const milli2 = <number>millisRunAt[1] const milli3 = <number>millisRunAt[2] const milli4 = <number>millisRunAt[3] const milliDiff1 = milli2 - milli1 const milliDiff2 = milli3 - milli2 const milliDiff3 = milli4 - milli3 expect(milliDiff1).toBeGreaterThan(500) expect(milliDiff2).toBeGreaterThan(500) expect(milliDiff3).toBeGreaterThan(500) }) it("Should execute Promises at max 2 per 1000ms", async () => { const queue = new ConcurrentPromiseQueue({ unitOfTimeMillis: 1000, maxThroughputPerUnitTime: 2, }) const ticketGenerator = new TicketGenerator() const generatePromise = () => { const start = new Date() return async () => { await sleep(100) const ticket = ticketGenerator.generate() const millisSinceStart = ticket.timestamp.getTime() - start.getTime() return millisSinceStart } } const millisRunAt:Array<number|null> = await Promise.all([ queue.addPromise(generatePromise()), queue.addPromise(generatePromise()), queue.addPromise(generatePromise()), queue.addPromise(generatePromise()), queue.addPromise(generatePromise()), queue.addPromise(generatePromise()), ]) if (millisRunAt.some(milli => milli === null)) { fail(`Some millis were null: ${millisRunAt.join(", ")}`) } const milli1 = <number>millisRunAt[0] const milli2 = <number>millisRunAt[1] const milli3 = <number>millisRunAt[2] const milli4 = <number>millisRunAt[3] const milli5 = <number>millisRunAt[4] const milli6 = <number>millisRunAt[5] expect(milli3 - milli1).toBeGreaterThan(1000) expect(milli4 - milli1).toBeGreaterThan(1000) expect(milli5 - milli3).toBeGreaterThan(1000) expect(milli6 - milli3).toBeGreaterThan(1000) }) it("Should resolve promises in resolution order by default", async () => { const queue = new ConcurrentPromiseQueue({ maxNumberOfConcurrentPromises: 2, }) const ticketGenerator = new TicketGenerator() const generatedTickets = new Array<Ticket>() const generatePromise = (sleepTimeMillis:number) => { const ticket = ticketGenerator.generate(); return async () => { await sleep(sleepTimeMillis) return ticket } } await Promise.all([ queue.addPromise(generatePromise(1000)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), ]) expect(generatedTickets.map(ti => ti.ticketNumber)).toEqual([1,2,3,4,0]) }); it("Should resolve in queue order if resolveInOrder is set", async () => { const queue = new ConcurrentPromiseQueue({ maxNumberOfConcurrentPromises: 2, resolveInOrder: true, }) const ticketGenerator = new TicketGenerator() const generatedTickets = new Array<Ticket>() const generatePromise = (sleepTimeMillis:number) => { const ticket = ticketGenerator.generate(); return async () => { await sleep(sleepTimeMillis) return ticket } } await Promise.all([ queue.addPromise(generatePromise(1000)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(1000)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), ]) expect(generatedTickets.map(ti => ti.ticketNumber)).toEqual([0,1,2,3,4,5]) }) it("resolveInOrder should not effect single concurrency execution", async () => { const queue = new ConcurrentPromiseQueue({ maxNumberOfConcurrentPromises: 1, resolveInOrder: true, }) const ticketGenerator = new TicketGenerator() const generatedTickets = new Array<Ticket>() const generatePromise = (sleepTimeMillis:number) => { const ticket = ticketGenerator.generate(); return async () => { await sleep(sleepTimeMillis) return ticket } } await Promise.all([ queue.addPromise(generatePromise(1000)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(1000)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), queue.addPromise(generatePromise(100)).then(ticket => generatedTickets.push(ticket)), ]) expect(generatedTickets.map(ti => ti.ticketNumber)).toEqual([0,1,2,3,4,5]) }) })