web-dev-qa-db-ja.com

Array.prototype.filterを非同期で使用するには?

バックグラウンド

オブジェクトの配列をフィルタリングしようとしています。フィルタリングする前に、それらを何らかの形式に変換する必要があり、この操作は非同期です。

 const convert = () => new Promise( resolve => {
     setTimeout( resolve, 1000 );
 });

そのため、私の最初の試みは、async/awaitを使用して次のようなことをすることでした。

const objs = [ { id: 1, data: "hello" }, { id: 2, data: "world"} ];

objs.filter( async ( obj ) => {
    await convert();
    return obj.data === "hello";
});

皆さんご存知かもしれませんが、Array.protoype.filterは、コールバックがtrueまたはfalseを返す必要がある関数です。 filterは同期です。前の例では、どれも返さず、Promiseを返します(すべての非同期関数はPromisesです)。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

推測できるように、前のコードは実際には機能しません...その仮定は正しいです。

問題

非同期関数でフィルターを機能させるために、私はstackoverflowをチェックし、このトピックを見つけました:

プロミスを返す関数で配列をフィルタリングする

残念ながら、選択された答えは非常に複雑であり、クラスを使用しています。これは私には役に立たないでしょう。その代わりに、機能的なアプローチで単純な関数を使用する、より単純なソリューションを探しています。

最後に1つの解決策があります。コールバック付きのマップを使用してフィルターをシミュレートします。

https://stackoverflow.com/a/46842181/1337392

しかし、フィルター関数を置き換えるのではなく、修正したいと考えていました。

ご質問

  • フィルター内に非同期関数を使用する方法はありますか?
  • そうでない場合、私ができる最も簡単な交換は何ですか?
24
Flame_Phoenix

非同期関数でフィルターを使用する方法はありません(少なくとも私が知っていること)。 promiseのコレクションでfilterを使用する最も簡単な方法は、Promise.allを使用してから、結果のコレクションに関数を適用することです。次のようになります。

const results = await Promise.all(your_promises)
const filtered_results = results.filter(res => //do your filtering here)

それが役に立てば幸い。

22
mcousillas

Scramjet fromArray/toArrayメソッドを使用...

const result = await scramjet.fromArray(arr)
                             .filter(async (item) => somePromiseReturningMethod(item))
                             .toArray();

それと同じくらい簡単です-コピー/貼り付けの準備ができた例です:

const scramjet = require('../../');

async function myAsyncFilterFunc(data) {
    return new Promise(res => {
        process.nextTick(res.bind(null, data % 2));
    });
}

async function x() {
    const x = await scramjet.fromArray([1,2,3,4,5])
        .filter(async (item) => myAsyncFilterFunc(item))
        .toArray();
    return x;
}

x().then(
    (out) => console.log(out),
    (err) => (console.error(err), process.exit(3)) // eslint-disable-line
);

Disclamer:私はscramjetの著者です。 :)

2

フィルターを呼び出す配列に並列配列を作成します。私の例では、isValidで、フィルターfuncからのすべての約束を待ちます。 filterのコールバックで、2番目の引数indexを使用して、並列配列にインデックスを付け、フィルター処理する必要があるかどうかを判断します。

// ===============================================
// common
// ===============================================
const isValid = async () => Math.random() > 0.5;
const values = Array(100).fill(42); // array of size 100 with all its values being 42


// ===============================================
// won't filter anything
// ===============================================
const filtered = values.filter(async v => await isValid());
console.log(filtered.length);


// ===============================================
// filters
// ===============================================
(async () => {
  const shouldFilter = await Promise.all(values.map(isValid));
  const filtered2 = values.filter((value, index) => shouldFilter[index]);

  console.log(filtered2.length);
})();

Promiseインスタンスには真の値があるため、この動作は理にかなっていますが、一目では直観的ではありません。

0
James T.