Array.fromAsync()
This blog post is about the ECMAScript proposal “Array.fromAsync
for JavaScript” by J. S. Choi. It introduces a static method for converting asynchronous iterables to Arrays.
Currently JavaScript provides several tools for working with synchronous iterables – for example:
function* createSyncIterable() {
yield 'a';
yield 'b';
yield 'c';
}
// Array-destructuring
const [elem0, elem1] = createSyncIterable();
assert.equal(elem0, 'a');
assert.equal(elem1, 'b');
// Spreading into Arrays
assert.deepEqual(
['>', ...createSyncIterable(), '<'],
['>', 'a', 'b', 'c', '<']
);
// Spreading into function arguments
const arr = [];
arr.push('>', ...createSyncIterable(), '<');
assert.deepEqual(
arr,
['>', 'a', 'b', 'c', '<']
);
// Array.from()
assert.deepEqual(
Array.from(createSyncIterable()),
['a', 'b', 'c']
);
assert.deepEqual(
Array.from(createSyncIterable(), s => s + s),
['aa', 'bb', 'cc']
);
// for-of loop
for (const x of createSyncIterable()) {
console.log(x);
}
// Output:
// a
// b
// c
For working with asynchronous iterables, we currently only have the for-await-of
loop.
Array.fromAsync()
Array.fromAsync()
is the asynchronous version of Array.from()
:
interface ArrayConstructor {
fromAsync<T>(
asyncIterable: AsyncIterable<T>
): Promise<Array<T>>;
fromAsync<T, U>(
asyncIterable: AsyncIterable<T>,
mapFn: (value: T, index: number) => U,
thisArg?: any
): Promise<Array<U>>;
// ···
}
Array.fromAsync()
accepts up to three arguments:
asyncIterable
is the asynchronous iterable that is converted to an Array.mapFn
lets us transform the iterated values before they are added to the Array that is returned. If we provide this argument, Array.fromAsync()
works similarly to the Array method .map()
.thisArg
lets us specify the value of this
for mapFn
.Given that the values in asyncIterable
can’t be collected synchronously, Array.fromAsync()
works asynchronously and returns a Promise for an Array. Therefore, we await
its results in the following example:
async function* createAsyncIterable() {
yield 1;
yield 2;
yield 3;
}
assert.deepEqual(
await Array.fromAsync(createAsyncIterable()),
[1, 2, 3]
);
assert.deepEqual(
await Array.fromAsync(createAsyncIterable(), x => x * x),
[1, 4, 9]
);
Array.fromAsync()
We could implement Array.fromAsync()
like this:
async function arrayFromAsync(
asyncIterable, mapFn=x=>x, thisArg=undefined
) {
const result = [];
for await (const elem of asyncIterable) {
result.push(mapFn.call(thisArg, elem));
}
return result;
}
In Node.js, readable web streams are asynchronously iterable. Therefore, we can use Array.fromAsync()
to collect all the chunks (pieces of data) of a ReadableStream in an Array.
We’ll read the following file data.txt
:
First line
Second line
This is the code:
import {Readable} from 'node:stream';
import * as fs from 'node:fs';
const readableStream = Readable.toWeb(
fs.createReadStream('data.txt', 'utf-8')
);
const chunks = await Array.fromAsync(readableStream);
assert.deepEqual(
chunks,
['First line\nSecond line']
);
Array.from()
” in “JavaScript for impatient programmers”