node.js - Javascript: シリアルとパラレルの混合処理

okwaves2024-01-25  8

私は自分の問題に対する ES6 ソリューションを探しています (これはよくあることだと思います...):

リモート プロバイダーのリストがあり、それぞれが 1 ページに項目のリストを公開し、他のページに各項目の詳細を公開しています。 (たとえば) 1 時間に 1 回、すべてのプロバイダーからすべてのアイテムの詳細を取得する必要があります。

すべてのプロバイダーのリストを並行してフェッチし、各リストが解決されたらすぐに、そのリストからすべての項目の詳細を並行してフェッチしたいと考えています (ECONNRESET エラーを避けるために、必要に応じてリクエストを抑制しますが、これはまた別の話です)。

これは私の暫定的な解決策の実装です。

    const providers = [
      {
        name: 'A',
        urlList: 'https://jsonplaceholder.typicode.com/users/?_start=0&_limit=1',
        urlItem: 'https://jsonplaceholder.typicode.com/posts?userId=%id%&_start=0&_limit=2',
      },
      {
        name: 'B',
        urlList: 'https://jsonplaceholder.typicode.com/users/?_start=2&_limit=2',
        urlItem: 'https://jsonplaceholder.typicode.com/posts?userId=%id%&_start=0&_limit=2',
      },
      {
        name: 'C',
        urlList: 'https://jsonplaceholder.typicode.com/users/?_start=4&_limit=2',
        urlItem: 'https://jsonplaceholder.typicode.com/posts?userId=%id%&_start=0&_limit=2',
      },
    ];
    
    const getList = (provider) => fetch(provider.urlList).then(res => res.json());
    
    const getItems = (provider, list) => Promise.all(
      list.map(item => {
        return fetch(provider.urlItem.replace("%id%", item.id)).then(res => res.json()).then(res => res.map(r => ({ provider: provider.name, name: item.name, ...r })));
      })
    );
    
    const providerFetch = async provider => {
      console.log(`providerFetch ${provider.name} start`);
      try {
        const list = await getList(provider);
        const items = await getItems(provider, list);
        return items;
      } catch (error) {
        console.error(`providerFetch ${provider.name} error: ${error}`);
      } finally {
        console.log(`providerFetch ${provider.name} done`);
      }
    }
    
    (async () => {
      console.log('start');
      const data = await Promise.all(
        providers.map(async provider => {
          const providerData = providerFetch(provider);
          return providerData;
        })
      );
      //console.dir(data, { depth: 3 });
      console.log(data.flat(2));
      console.log('end');
    })();

これには少なくとも 2 つの問題があります:

Promise.all() を 2 回使用しているので、3 次元の配列を取得しますが、単純な 1 次元の配列を好みます。配列... Promise.all() で Promise の結果をプッシュせずに連結させる方法はありますか? 最も深刻な問題は、すべてのプロバイダーのリストをフェッチしていて、すべての準備が整った場合にのみ項目のフェッチを開始することです (予想通り、並行して)。代わりに、リストがフェッチされるとすぐに、各リストの項目のフェッチを開始したいと考えています。それを達成するためにコードを変更する方法のヒントを教えていただけますか?

更新: @Bergi のコメントのおかげで、次のことが理解できました。

ポイント 1.: これは、最終データに対して flat(2) (2 は平坦化する深さ) を使用して簡単に解決できます。解決策はそれほど洗練されていませんが、promise.All() はプッシュのみが可能で、連結はできないようです。それを示すためにコードサンプルを変更しました。 ポイント 2.: ProviderFetch がシリアルに動作しているという事実は単なる仮定でした。私の場合、おそらくこのテスト コードでは getList が非常に高速であるためです...実際、getList が返される前に遅延を追加すると、すべての getList が終了する前に一部の getItem が開始されることがわかります。

これをチェックしてください... stackoverflow.com/q/63173059/10872163

– ジェクス

2020 年 9 月 3 日 16:27

1

「私はすべてのプロバイダー リストをフェッチし、すべての準備が整った場合にのみアイテムのフェッチを開始します。 - ええと、いいえ、そうではありません。 ProviderFetch は、getList が完了した直後に getItems を呼び出しています。コードには、すべてのリストを待機するものはありません。

– ベルギ

2020 年 9 月 3 日 19:32

1

「Promise.all() を 2 回使用しているため、3 次元配列が得られます」 - 2 つの Promise.all をネストすると、2 次元配列が得られます。 3次元の由来は、getItems は配列を取得します。

– ベルギ

2020 年 9 月 3 日 19:34

「Promise.all() で Promise の結果を連結する方法はありますか?」 - いいえ、そうではありませんが、Promise.all() プロミスが完了した後 (.then コールバックまたは await 後)、ネストされた配列をフラット化するのは簡単です。

– ベルギ

2020 年 9 月 3 日 19:35



------------------------

Promise.all 自体には concat 機能はありませんが、JavaScript arraそうします。したがって、後で結果を連結するだけです。

const result = await Promise.all(/* your original code ... */);

let data = [];

result.forEach(x => data = data.concat(x));

// now data is as you expect

3

providerFetch は配列を返さない (返せない)

– ベルギ

2020 年 9 月 4 日 0:26

@Bergi まだ答えを書き終えていません

– スレベットマン

2020 年 9 月 4 日 0:28

@Bergi 連続して 2 つの約束を待っていることに気づきませんでした

– スレベットマン

2020 年 9 月 4 日 0:34

総合生活情報サイト - OKWAVES
総合生活情報サイト - OKWAVES
生活総合情報サイトokwaves(オールアバウト)。その道のプロ(専門家)が、日常生活をより豊かに快適にするノウハウから業界の最新動向、読み物コラムまで、多彩なコンテンツを発信。