I think @bakkot gives some persuasive points, especially that the mapping function is actually essentially an async function, so it wouldn’t make sense for its identity to be x => x.
My priorities, in order, have always been:
-
Array.fromAsync(i)must be equivalent toArray.fromAsync(i, undefined)andArray.fromAsync(i, null). (For optional parameters, nullish arguments should be functionally equivalent to omitting the arguments. This is how every function in the core language is designed, and I believe it is also an explicit best practice in Web APIs.) -
Array.fromAsync(i)must be equivalent tofor await (const v of i). (The default case offromAsyncmust match intuitions aboutfor await (of), just like howfrommatches intuitions aboutfor (of).) -
Array.fromAsync(i)should be equivalent toAsyncIterator.from(i).toArray(). -
Array.fromAsync(i, f)should be equivalent toAsyncIterator.from(i).map(f).toArray(). -
Array.fromAsync(i, f)should conceptually but not strictly be equivalent toArray.from(i, f).
I lost sight of the second priority when I wrote #20.
Bakkot points out that the default mapping function of Array.fromAsync does not have to be x => x, and omitting the mapping function does not have to be equivalent to specifying x => x or some other function.
Therefore, I plan to revert my changes in #20. The default behavior without a mapping function will be to not await values yielded by async iterators. When a mapping function is given, the inputs supplied to the mapping function will be the values yielded by the input async iterator without awaiting; only the results of the mapping function will be awaited. This behavior should match AsyncIterator.prototype.toArray.
function createIt () {
return {
[Symbol.asyncIterator]() {
let i = 1;
return {
async next() {
if (i > 2) {
return { done: true };
}
i++;
return { value: Promise.resolve(i), done: false }
},
};
},
};
}Without any mapping function:
result = [];
for await (const x of createIt()) {
console.log(x);
result.push(x);
}
// result is [ Promise.resolve(1), Promise.resolve(2), Promise.resolve(3) ].
result = await Array.fromAsync(createIt());
// result is [ Promise.resolve(1), Promise.resolve(2), Promise.resolve(3) ].With mapping function x => x:
result = await Array.fromAsync(createIt(), x => x);
// result is [ 1, 2, 3 ].
result = await AsyncIterator.from(createIt())
.map(x => x)
.toArray();
// result is [ 1, 2, 3 ].With mapping function x => (console.log(x), x):
result = await Array.fromAsync(createIt(), x =>
(console.log(x), x));
// Prints three promises.
// result is [ 1, 2, 3 ].
result = await AsyncIterator.from(createIt())
.map(x => (console.log(x), x))
.toArray();
// Prints three promises.
// result is [ 1, 2, 3 ].With mapping function async x => (console.log(await x), await x)):
result = await Array.fromAsync(createIt(), async x =>
(console.log(await x), await x));
// Prints three promises.
// result is [ 1, 2, 3 ].
result = await AsyncIterator.from(createIt())
.map(async x => (console.log(await x), await x))
.toArray();
// Prints 1, 2, then 3.
// result is [ 1, 2, 3 ].