modern-async
Version:
A modern tooling library for asynchronous operations using async/await, promises and async generators
704 lines (620 loc) • 17.9 kB
JavaScript
/* eslint-disable require-jsdoc, jsdoc/require-jsdoc */
import { expect, test } from '@jest/globals'
import asyncGeneratorMap from './asyncGeneratorMap.mjs'
import { range } from 'itertools'
import asyncDelay from './asyncDelay.mjs'
import Queue from './Queue.mjs'
import Deferred from './Deferred.mjs'
import asyncSleep from './asyncSleep'
import asyncIterableToArray from './asyncIterableToArray.mjs'
class TestError extends Error {}
test('asyncGeneratorMap base', async () => {
const res = []
for await (const el of asyncGeneratorMap(range(6), async (x) => x * 2, new Queue(1))) {
res.push(el)
}
expect(res).toEqual([0, 2, 4, 6, 8, 10])
})
test('asyncGeneratorMap all resolve in micro tasks', async () => {
const del = asyncDelay()
const gen = asyncGeneratorMap(range(6), async (x) => x, new Queue(1))
let finished = false
;(async () => {
const res = []
for await (const el of gen) {
res.push(el)
}
return res
})().then(async (res) => {
expect(res).toStrictEqual([...range(6)])
finished = true
})
await del
expect(finished).toBe(true)
})
test('asyncGeneratorMap infinity all resolve in micro tasks', async () => {
const del = asyncDelay()
const gen = asyncGeneratorMap(range(6), async (x) => x, new Queue(Number.POSITIVE_INFINITY))
let finished = false
;(async () => {
const res = []
for await (const el of gen) {
res.push(el)
}
return res
})().then(async (res) => {
expect(res).toStrictEqual([...range(6)])
finished = true
})
await del
expect(finished).toBe(true)
})
test('asyncGeneratorMap same queue one level concurrency 1 random delays', async () => {
const queue = new Queue(10)
const gen1 = asyncGeneratorMap(range(1000), async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const p = (async () => {
const res = []
for await (const el of gen1) {
res.push(el)
}
return res
})()
const res = await p
expect(res).toStrictEqual([...range(1000)].map((x) => x * 2))
})
test('asyncGeneratorMap same queue one level concurrency 10 random delays', async () => {
const queue = new Queue(10)
const gen1 = asyncGeneratorMap(range(1000), async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const p = (async () => {
const res = []
for await (const el of gen1) {
res.push(el)
}
return res
})()
const res = await p
expect(res).toStrictEqual([...range(1000)].map((x) => x * 2))
})
test('asyncGeneratorMap same queue one level concurrency 3 busy queue random delays', async () => {
const queue = new Queue(10)
;[...range(7)].forEach(element => {
queue.exec(async () => new Promise(() => {}))
})
const gen1 = asyncGeneratorMap(range(1000), async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const p = (async () => {
const res = []
for await (const el of gen1) {
res.push(el)
}
return res
})()
const res = await p
expect(res).toStrictEqual([...range(1000)].map((x) => x * 2))
}, 10000)
test('asyncGeneratorMap same queue three levels concurrency 1', async () => {
const callList = []
const queue = new Queue(1)
const gen1 = asyncGeneratorMap(range(3), async (x, i) => {
callList.push([0, i])
return x * 2
}, queue)
const gen2 = asyncGeneratorMap(gen1, async (x, i) => {
callList.push([1, i])
return x * 2
}, queue)
const gen3 = asyncGeneratorMap(gen2, async (x, i) => {
callList.push([2, i])
return x * 2
}, queue)
const p = (async () => {
const res = []
for await (const el of gen3) {
res.push(el)
}
return res
})()
const res = await p
expect(res).toStrictEqual([0, 8, 16])
})
test('asyncGeneratorMap same queue three levels concurrency 2', async () => {
const callList = []
const queue = new Queue(2)
const gen1 = asyncGeneratorMap(range(3), async (x, i) => {
callList.push([0, i])
return x * 2
}, queue)
const gen2 = asyncGeneratorMap(gen1, async (x, i) => {
callList.push([1, i])
return x * 2
}, queue)
const gen3 = asyncGeneratorMap(gen2, async (x, i) => {
callList.push([2, i])
return x * 2
}, queue)
const p = (async () => {
const res = []
for await (const el of gen3) {
res.push(el)
}
return res
})()
const res = await p
expect(res).toStrictEqual([0, 8, 16])
})
test('asyncGeneratorMap same queue three levels concurrency infinity', async () => {
const callList = []
const queue = new Queue(Number.POSITIVE_INFINITY)
const gen1 = asyncGeneratorMap(range(3), async (x, i) => {
callList.push([0, i])
return x * 2
}, queue)
const gen2 = asyncGeneratorMap(gen1, async (x, i) => {
callList.push([1, i])
return x * 2
}, queue)
const gen3 = asyncGeneratorMap(gen2, async (x, i) => {
callList.push([2, i])
return x * 2
}, queue)
const p = (async () => {
const res = []
for await (const el of gen3) {
res.push(el)
}
return res
})()
const res = await p
expect(res).toStrictEqual([0, 8, 16])
})
test('asyncGeneratorMap same queue three levels concurrency 1 random delays', async () => {
const queue = new Queue(1)
const gen1 = asyncGeneratorMap(range(100), async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const gen2 = asyncGeneratorMap(gen1, async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const gen3 = asyncGeneratorMap(gen2, async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const p = (async () => {
const res = []
for await (const el of gen3) {
res.push(el)
}
return res
})()
const res = await p
expect(res).toStrictEqual([...range(100)].map((x) => x * 8))
})
test('asyncGeneratorMap same queue three levels concurrency 5 random delays', async () => {
const queue = new Queue(5)
const gen1 = asyncGeneratorMap(range(100), async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue, true)
const gen2 = asyncGeneratorMap(gen1, async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue, true)
const gen3 = asyncGeneratorMap(gen2, async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue, true)
const p = (async () => {
const res = []
for await (const el of gen3) {
res.push(el)
}
return res
})()
const res = await p
expect(res).toStrictEqual([...range(100)].map((x) => x * 8))
})
test('asyncGeneratorMap same queue three levels infinity random delays', async () => {
const queue = new Queue(Number.POSITIVE_INFINITY)
const gen1 = asyncGeneratorMap(range(100), async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const gen2 = asyncGeneratorMap(gen1, async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const gen3 = asyncGeneratorMap(gen2, async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const p = (async () => {
const res = []
for await (const el of gen3) {
res.push(el)
}
return res
})()
const res = await p
expect(res).toStrictEqual([...range(100)].map((x) => x * 8))
})
test('asyncGeneratorMap same queue three levels busy queue random delays ', async () => {
const queue = new Queue(10)
;[...range(7)].forEach(element => {
queue.exec(async () => new Promise(() => {}))
})
const gen1 = asyncGeneratorMap(range(100), async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const gen2 = asyncGeneratorMap(gen1, async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const gen3 = asyncGeneratorMap(gen2, async (x, i) => {
await asyncSleep(Math.floor(Math.random() * 10))
return x * 2
}, queue)
const p = (async () => {
const res = []
for await (const el of gen3) {
res.push(el)
}
return res
})()
const res = await p
expect(res).toStrictEqual([...range(100)].map((x) => x * 8))
})
test('asyncGeneratorMap fail fetch first', async () => {
const bd = [...range(2)].map(() => new Deferred())
const originGen = asyncGeneratorMap(range(2), async (x, i) => {
await bd[i].promise
if (i === 0) {
throw new TestError()
} else {
return x
}
}, new Queue(2))
const gen = asyncGeneratorMap(originGen, async (x) => {
return (x + 1) * 2
}, new Queue(2))
const p1 = gen.next()
bd[1].resolve()
await asyncDelay()
bd[0].resolve()
try {
await p1
expect(false).toBe(true)
} catch (e) {
expect(e instanceof TestError).toBe(true)
}
})
test('asyncGeneratorMap fail fetch second', async () => {
const bd = [...range(2)].map(() => new Deferred())
const originGen = asyncGeneratorMap(range(2), async (x, i) => {
await bd[i].promise
if (i === 1) {
throw new TestError()
} else {
return x
}
}, new Queue(2))
const gen = asyncGeneratorMap(originGen, async (x) => {
return (x + 1) * 2
}, new Queue(2))
const p1 = gen.next()
bd[1].resolve()
await asyncDelay()
bd[0].resolve()
const res = await p1
expect(res.done).toStrictEqual(false)
expect(res.value).toStrictEqual(2)
const p2 = gen.next()
try {
await p2
expect(false).toBe(true)
} catch (e) {
expect(e instanceof TestError).toBe(true)
}
})
test('asyncGeneratorMap fail process first', async () => {
const bd = [...range(2)].map(() => new Deferred())
const gen = asyncGeneratorMap(range(2), async (x, i) => {
await bd[i].promise
if (i === 0) {
throw new TestError()
} else {
return (x + 1) * 2
}
}, new Queue(2))
const p1 = gen.next()
bd[1].resolve()
await asyncDelay()
bd[0].resolve()
try {
await p1
expect(false).toBe(true)
} catch (e) {
expect(e instanceof TestError).toBe(true)
}
})
test('asyncGeneratorMap fail process second', async () => {
const bd = [...range(2)].map(() => new Deferred())
const gen = asyncGeneratorMap(range(2), async (x, i) => {
await bd[i].promise
if (i === 1) {
throw new TestError()
} else {
return (x + 1) * 2
}
}, new Queue(2))
const p1 = gen.next()
bd[1].resolve()
await asyncDelay()
bd[0].resolve()
const res = await p1
expect(res.done).toStrictEqual(false)
expect(res.value).toStrictEqual(2)
const p2 = gen.next()
try {
await p2
expect(false).toBe(true)
} catch (e) {
expect(e instanceof TestError).toBe(true)
}
})
test('asyncGeneratorMap fail fetch first unordered', async () => {
const bd = [...range(2)].map(() => new Deferred())
const originGen = asyncGeneratorMap(range(2), async (x, i) => {
await bd[i].promise
if (i === 0) {
throw new TestError()
} else {
return x
}
}, new Queue(2))
const gen = asyncGeneratorMap(originGen, async (x) => {
return (x + 1) * 2
}, new Queue(2), false)
const p1 = gen.next()
bd[1].resolve()
await asyncDelay()
bd[0].resolve()
try {
await p1
expect(false).toBe(true)
} catch (e) {
expect(e instanceof TestError).toBe(true)
}
})
test('asyncGeneratorMap fail fetch second unordered', async () => {
const originGen = asyncGeneratorMap(range(2), async (x, i) => {
if (i === 1) {
throw new TestError()
} else {
return x
}
}, new Queue(2))
const bd = [...range(2)].map(() => new Deferred())
const gen = asyncGeneratorMap(originGen, async (x, i) => {
await bd[i].promise
return (x + 1) * 2
}, new Queue(2), false)
const p1 = gen.next().then((r) => ['resolved', r], (e) => ['rejected', e])
await asyncDelay()
bd[0].resolve()
const [state, result] = await p1
expect(state).toStrictEqual('rejected')
expect(result instanceof TestError).toBe(true)
})
test('asyncGeneratorMap fail process first unordered', async () => {
const bd = [...range(2)].map(() => new Deferred())
const gen = asyncGeneratorMap(range(2), async (x, i) => {
await bd[i].promise
if (i === 0) {
throw new TestError()
} else {
return (x + 1) * 2
}
}, new Queue(2), false)
const p1 = gen.next()
bd[1].resolve()
const res = await p1
expect(res.done).toStrictEqual(false)
expect(res.value).toStrictEqual(4)
const p2 = gen.next()
bd[0].resolve()
try {
await p2
expect(false).toBe(true)
} catch (e) {
expect(e instanceof TestError).toBe(true)
}
})
test('asyncGeneratorMap fail process second unordered', async () => {
const bd = [...range(2)].map(() => new Deferred())
const gen = asyncGeneratorMap(range(2), async (x, i) => {
await bd[i].promise
if (i === 1) {
throw new TestError()
} else {
return (x + 1) * 2
}
}, new Queue(2), false)
const p1 = gen.next()
bd[1].resolve()
try {
await p1
expect(false).toBe(true)
} catch (e) {
expect(e instanceof TestError).toBe(true)
}
})
test('asyncGeneratorMap unordered fail in fetch', async () => {
const originGen = asyncGeneratorMap(range(2), async (x, i) => {
throw new TestError()
}, Number.POSITIVE_INFINITY)
const callList = []
const gen = asyncGeneratorMap(originGen, async (x, i) => {
callList.push(i)
return (x + 1) * 2
}, 1, false)
const p1 = gen.next().then((r) => ['resolved', r], (e) => ['rejected', e])
const [state, result] = await p1
expect(state).toStrictEqual('rejected')
expect(result).toBeInstanceOf(TestError)
await asyncDelay()
expect(callList).toStrictEqual([])
})
test('asyncGeneratorMap cancel scheduled busy queue', async () => {
const queue = new Queue(100)
const d = new Deferred()
const waiting = [...range(99)].map(() => {
return queue.exec(async () => await d.promise)
})
const callList = []
const gen = asyncGeneratorMap(range(100), async (x, i) => {
callList.push(i)
await asyncSleep(1)
if (i === 50) {
throw new TestError()
}
return x
}, queue)
const [state, result] = await asyncIterableToArray(gen).then((r) => ['resolved', r], (e) => ['rejected', e])
expect(state).toStrictEqual('rejected')
expect(result).toBeInstanceOf(TestError)
d.resolve()
await Promise.all(waiting)
await asyncDelay()
expect(callList).toStrictEqual([...range(51)])
})
test('asyncGeneratorMap infinite sync operator', async () => {
let shouldStop = false
const infiniteSyncGenerator = function * () {
let i = 0
while (true) {
if (shouldStop) {
return
}
yield i
i += 1
}
}
const results = []
for await (const el of asyncGeneratorMap(infiniteSyncGenerator(), async (el) => {
await asyncSleep(1)
if (el === 10) {
shouldStop = true
}
return el * 2
})) {
results.push(el)
}
expect(shouldStop).toStrictEqual(true)
expect(results.length).toBeGreaterThanOrEqual(1)
expect(results[0]).toStrictEqual(0)
expect(results[1]).toStrictEqual(2)
expect(results[2]).toStrictEqual(4)
})
test('asyncGeneratorMap blocks when concurrency reached', async () => {
const callList = []
const d = new Deferred()
const p = asyncIterableToArray(asyncGeneratorMap(range(10), async (el, i) => {
callList.push(i)
await d.promise
return el
}, 3))
await asyncDelay()
expect(callList).toStrictEqual([0, 1, 2])
d.resolve()
const result = await p
expect(result).toStrictEqual(await asyncIterableToArray(range(10)))
})
test('asyncGeneratorMap reaches concurrency', async () => {
const expectedConcurrency = 10
let currentConcurrency = 0
let maxConcurrency = 0
const d = new Deferred()
const p = asyncIterableToArray(asyncGeneratorMap(range(100), async (el, i) => {
currentConcurrency += 1
maxConcurrency = Math.max(maxConcurrency, currentConcurrency)
await d.promise
currentConcurrency -= 1
maxConcurrency = Math.max(maxConcurrency, currentConcurrency)
return el
}, expectedConcurrency))
await asyncDelay()
expect(maxConcurrency).toStrictEqual(expectedConcurrency)
d.resolve()
const result = await p
expect(maxConcurrency).toStrictEqual(expectedConcurrency)
expect(result).toStrictEqual(await asyncIterableToArray(range(100)))
})
test('common for await interrupts generator on interrupted iteration', async () => {
let finallyReached = false
async function * asyncGenWithFinally () {
try {
for (const element of range(10)) {
yield element
}
} finally {
finallyReached = true
}
}
for await (const n of asyncGenWithFinally()) {
if (n === 2) {
break
}
}
expect(finallyReached).toBe(true)
})
test('common for await calls AsyncGenerator.return() not AsyncGenerator.throw()', async () => {
let catchedException = null
async function * asyncGenWithFinally () {
try {
for (const element of range(10)) {
yield element
}
} catch (e) {
catchedException = e
}
}
try {
for await (const n of asyncGenWithFinally()) {
if (n === 2) {
throw new TestError()
}
}
} catch (e) {
expect(e instanceof TestError).toBe(true)
}
expect(catchedException).toBe(null)
})
test('asyncGeneratorMap interrupts generator on interrupted iteration', async () => {
let finallyReached = false
async function * asyncGenWithFinally () {
try {
for (const element of range(10)) {
yield element
}
} finally {
finallyReached = true
}
}
for await (const n of asyncGeneratorMap(asyncGenWithFinally(), n => n * 2)) {
if (n === 4) {
break
}
}
expect(finallyReached).toBe(true)
})