iterable & iterator are ancient concepts in programming languages. Something is iterable because it has a iterator which we can use to iterate it. Their relation and interfaces are as follows:

relation
relation
1
2
3
4
5
6
7
8
9
10
11
12
interface Iterable<T> {
[Symbol.iterator]() : Iterator<T>;
}

interface Iterator<T> {
next() : IteratorResult<T>;
}

interface IteratorResult<T> {
value: T;
done: boolean;
}

A generator is just both an iterable & iterator. You can use it wherever an iterable can be used, e.g. for...of.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function* genFun() {
yield 1;
yield 2;
}

const gor = genFun();

// gor is iterable, we get its itertor
const itor = gor[Symbol.iterator]();

// gor is also a itertor, we can call its next method directly
console.log(gor.next());

console.log(itor.next());

for (let i of gor) {
console.log(i); // 1 2
}

We say they(iteration & generator) are sync because we get the data immediately by calling next(). What if next() returns a promise:

1
2
3
4
5
6
7
8
9
10
interface AsyncIterable<T> {
[Symbol.asyncIterator]() : AsyncIterator<T>;
}
interface AsyncIterator<T> {
next() : Promise<IteratorResult<T>>; // (A)
}
interface IteratorResult<T> {
value: T;
done: boolean;
}

We call it async iteratle & iterator. async iterable can be used in for...wait...of:

1
2
3
4
5
6
7
const arr = [Promise.resolve('a'), Promise.resolve('b')];
for await (const x of arr) {
console.log(x);
}
// Output:
// 'a'
// 'b'

Now we can talk about async generator. As contrasted with sync generator, async generator is just both a async iterable & iterator. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async function* yield123() {
for (let i=1; i<=3; i++) {
yield i;
}
}

const asyncIterable = yield123();

// or just *const asyncIterator = asyncIterable;*
const asyncIterator = asyncIterable[Symbol.asyncIterator]();

console.log(await asyncIterator.next()); // { value: 1, done: false }
console.log(await asyncIterator.next()); // { value: 2, done: false }
console.log(await asyncIterator.next()); // { value: 3, done: false }
console.log(await asyncIterator.next()); // { value: undefined, done: false }

for await (const x of yield123()) {
console.log(x); // 1 2 3
}

sync generator yield a normal value while async generator yield a promise. That’s the only difference(off course, we should add async key word before the generator function).

Another example: transforming an async iterable

1
2
3
4
5
async function* timesTwo(asyncNumbers) {
for await (const x of asyncNumbers) {
yield x * 2;
}
}

Last real example from async-pool: throttle a bunch of requests with a max count of parallel ongoing requests:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function* asyncPool(concurrency, iterable, iteratorFn) {
const executing = new Set();
async function consume() {
const [promise, value] = await Promise.race(executing);
executing.delete(promise);
return value;
}
for (const item of iterable) {
// Wrap iteratorFn() in an async fn to ensure we get a promise.
// Then expose such promise, so it's possible to later reference and
// remove it from the executing pool.
const promise = (async () => await iteratorFn(item, iterable))().then(
value => [promise, value]
);
executing.add(promise);
if (executing.size >= concurrency) {
yield await consume();
}
}
while (executing.size) {
yield await consume();
}
}

Usage of it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const timeout = i =>
new Promise(resolve =>
setTimeout(() => {
resolve(i);
}, i)
);

const gen = asyncPool(2, [10, 50, 30, 20], timeout);

async function check() {
for await (const t of gen) {
console.log(t);
}
}

check();

Another version with just async function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
export type RequestToMake = () => Promise<void>;

/**
* Given an array of requestsToMake and a limit on the number of max parallel requests
* queue up those requests and start firing them
* - inspired by Rafael Xavier's approach here: https://stackoverflow.com/a/48007240/761388
*
* @param requestsToMake
* @param maxParallelRequests the maximum number of requests to make - defaults to 6
*/
async function throttleRequests(
requestsToMake: RequestToMake[],
maxParallelRequests = 6
) {
// queue up simultaneous calls
const queue: Promise<void>[] = [];
for (let requestToMake of requestsToMake) {
// fire the async function, add its promise to the queue,
// and remove it from queue when complete
const promise = requestToMake().then((res) => {
queue.splice(queue.indexOf(promise), 1);
return res;
});
queue.push(promise);

// if the number of queued requests matches our limit then
// wait for one to finish before enqueueing more
if (queue.length >= maxParallelRequests) {
await Promise.race(queue);
}
}
// wait for the rest of the calls to finish
await Promise.all(queue);
}