I wanted to add that the fact Promise
is eager also has performance implications.
const asyncFlatMap = <A, B>(arr: A[], f: (a: A) => Promise<B>) =>
Promise.all(arr.map(f)).then(arr => arr.flat());
asyncFlatMap([[1], [2], [3]], async nums => nums); // Promise<number[]>
You can see in the code above that we need to map
, then flat
, with this additional round trip through the microtasks queue because of the then
in-between. Doing this kind of round trip for the sake of playing Lego is wasteful.
Note that if you don't write an asyncFlatMap
helper you can avoid that then
of course. I'm making a general statement here.
If you happen to nest promises a lot you may be interested in Fluture.
Futures are lazy, so you can play Lego with them before anything is run.
import { resolve, parallel, promise, map } from 'fluture';
import { pipe } from 'fp-ts/function';
const numbersFlatten = await pipe(
numbers.map(nums => resolve(nums)), // Future<number[]>[]
parallel(10), // Future<number[][]>
map(x => x.flat()), // Future<number[]>
promise // Promise<number[]>
);
In the code above I converted the Future back to a Promise with promise
in order to match your use case. It is only when this function is called that the computation is run. fork
would also run the computation without ever involving promises.
In case you are not familiar with pipe
: the first argument is the value which is threaded through the remaining function arguments.
The magic value 10
in parallel
is just the maximum number of computations to run concurrently. It could have been anything else.