表題の通りなんですが、JavaScriptで配列のfilterにasync関数を使う方法を調べていたところ、StackOverflowでカッコいい書き方を見つけたので紹介します。
いつ必要なのか?
次のコードのように、Array.prototype.filter(callback)
を使えば配列から特定の条件を満たす要素だけを抜き出すことができます。
const arr = [1,2,3,4,5,6];
console.log(arr.filter(n => n % 2 == 0)); // [2,4,6]
このコールバック関数は配列の要素を受け取ってbooleanを返すような関数である必要があります。
ところが、このコールバックとしてasync関数を書きたいことがあります。つまり、返り値が Promise<boolean>
である関数でfilterしたいというわけです。
そんなことあるのかって言われると困るんですが、先ほど人生で初めて必要になりました。
スマートな解
私が良いと思った実装は次のようなものです(注:当初より随分シンプルになりました)。
async function asyncFilter(array, asyncCallback) {
const bits = await Promise.all(array.map(asyncCallback));
return array.filter((_, i) => bits[i]);
}
この実装について解説します。
コールバック関数がasync関数なので、処理したい配列にいきなりfilter()
を適用するわけにいきません。そこでまずmap()
を適用してPromiseの配列にします。そしてPromise.all()
でbooleanの配列のPromiseにして、awaitで受ければPromiseがresolveされてbooleanの配列が得られます。
このbooleanの配列をfilter()
のコールバック関数で利用します。コールバック関数の第2引数は配列のindexなので、これを使って対応する真偽値を取り出します。(コメントいただいたttatsfさん、41semicolonさん、ありがとうございました!)
利用例
Puppeteerのpage.$$()
で取り出した要素のうち、viewport内に見えている要素だけを取り出したい場合、下記のように書けます。
const errorBlocks = await my.page.$$('div.error').then(
els => asyncFilter(els, el => el.isIntersectingViewport())
);
なかなか実用的ですね!