2283

async/awaitforEachループで使用する際に問題はありますか? awaitファイルの配列と各ファイルの内容をループしようとしています。

import fs from 'fs-promise'

async function printFiles () {
  const files = await getFilePaths() // Assume this works fine

  files.forEach(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  })
}

printFiles()

このコードは機能しますが、何か問題が発生する可能性はありますか? このような高階関数でasync/を使用することは想定されていないと誰かに言われたので、これに問題があるかどうかを尋ねたかっただけです。await

4

32 に答える 32

4231

確かにコードは機能しますが、期待どおりに動作しないことは確かです。複数の非同期呼び出しを開始するだけですが、printFiles関数はその後すぐに戻ります。

順番に読む

ファイルを順番に読みたい場合は、indeedは使えません。forEach代わりに最新のfor … ofループを使用するだけでawait、期待どおりに機能します。

async function printFiles () {
  const files = await getFilePaths();

  for (const file of files) {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }
}

並行して読む

ファイルを並行して読み込みたい場合、indeedは使用できません。forEachコールバック関数呼び出しのそれぞれはasync約束を返しますが、それらを待つのではなく捨てています。代わりに使用するだけmapで、次のようにして得られるプロミスの配列を待つことができますPromise.all:

async function printFiles () {
  const files = await getFilePaths();

  await Promise.all(files.map(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  }));
}
于 2016-06-01T19:02:09.033 に答える
432

ES2018 を使用すると、上記のすべての回答を次のように大幅に簡素化できます。

async function printFiles () {
  const files = await getFilePaths()

  for await (const contents of files.map(file => fs.readFile(file, 'utf8'))) {
    console.log(contents)
  }
}

仕様を参照してください:提案-非同期-反復


2018-09-10: この回答は最近多くの注目を集めています。非同期反復の詳細については、 Axel Rauschmayer のブログ投稿を参照してください。

于 2018-06-15T11:17:50.153 に答える
5

今日、これに対する複数のソリューションに出会いました。forEach ループで async await 関数を実行します。ラッパーを構築することで、これを実現できます。

内部でどのように機能するか、ネイティブの forEach について、および非同期関数呼び出しを作成できない理由と、さまざまなメソッドに関するその他の詳細についての詳細な説明は、こちらのリンクで提供されています。

それを行うことができる複数の方法とそれらは次のとおりです。

方法 1 : ラッパーを使用する。

await (()=>{
     return new Promise((resolve,reject)=>{
       items.forEach(async (item,index)=>{
           try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
           count++;
           if(index === items.length-1){
             resolve('Done')
           }
         });
     });
    })();

方法 2: Array.prototype のジェネリック関数と同じものを使用する

Array.prototype.forEachAsync.js

if(!Array.prototype.forEachAsync) {
    Array.prototype.forEachAsync = function (fn){
      return new Promise((resolve,reject)=>{
        this.forEach(async(item,index,array)=>{
            await fn(item,index,array);
            if(index === array.length-1){
                resolve('done');
            }
        })
      });
    };
  }

使用法 :

require('./Array.prototype.forEachAsync');

let count = 0;

let hello = async (items) => {

// Method 1 - Using the Array.prototype.forEach 

    await items.forEachAsync(async () => {
         try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
        count++;
    });

    console.log("count = " + count);
}

someAPICall = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("done") // or reject('error')
        }, 100);
    })
}

hello(['', '', '', '']); // hello([]) empty array is also be handled by default

方法 3 :

Promise.all の使用

  await Promise.all(items.map(async (item) => {
        await someAPICall();
        count++;
    }));

    console.log("count = " + count);

方法 4 : 従来の for ループまたは最新の for ループ

// Method 4 - using for loop directly

// 1. Using the modern for(.. in..) loop
   for(item in items){

        await someAPICall();
        count++;
    }

//2. Using the traditional for loop 

    for(let i=0;i<items.length;i++){

        await someAPICall();
        count++;
    }


    console.log("count = " + count);
于 2019-11-24T20:31:54.190 に答える
3

forEach ループで async を使用する優れた例を次に示します。

独自の asyncForEach を作成する

async function asyncForEach(array, callback) {  
    for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array)
    }
}

こんな感じで使えます

await asyncForEach(array, async function(item,index,array){
     //await here
   }
)
于 2021-02-11T01:45:56.730 に答える
2

OPの元の質問

forEach ループで async/await を使用する際に問題はありますか? ...

@Bergi の選択された回答である程度カバーされ、シリアルおよびパラレルで処理する方法が示されました。ただし、並列処理には他にも問題があります -

  1. 注文 -- @chharveyは次のことに注意してください --

たとえば、非常に大きなファイルの前に非常に小さなファイルの読み取りが終了した場合、小さなファイルが files 配列内の大きなファイルの後に来る場合でも、最初にログに記録されます。

  1. 一度にあまりにも多くのファイルを開く可能性があります - 別の回答の下でのBergiによるコメント

また、何千ものファイルを一度に開いて同時に読み取るのもよくありません。シーケンシャル、パラレル、または混合アプローチが優れているかどうかを常に評価する必要があります。

そこで、簡潔で簡潔で、サードパーティのライブラリを使用しない実際のコードを示して、これらの問題に対処しましょう。簡単に切り取り、貼り付け、変更できるもの。

並列読み取り (一度にすべて)、順次印刷 (ファイルごとにできるだけ早く)。

最も簡単な改善は、@ Bergi の回答のように完全な並列処理を実行することですが、順序を維持しながら各ファイルができるだけ早く印刷されるように小さな変更を加えます。

async function printFiles2() {
  const readProms = (await getFilePaths()).map((file) =>
    fs.readFile(file, "utf8")
  );
  await Promise.all([
    await Promise.all(readProms),                      // branch 1
    (async () => {                                     // branch 2
      for (const p of readProms) console.log(await p);
    })(),
  ]);
}

上記では、2 つの別々のブランチが同時に実行されています。

  • ブランチ 1: 並列読み取り、一度に、
  • ブランチ 2: 順序を強制するためにシリアルで読み取りますが、必要以上に待機していません

それは簡単でした。

同時実行制限で並行して読み取り、シリアルで印刷します (ファイルごとにできるだけ早く)。

「同時実行制限」とは、N一度に読み取られるファイルが最大になることを意味します。
一度に非常に多くの顧客のみを許可する店のように(少なくともCOVID中)。

最初にヘルパー関数が導入されます -

function bootablePromise(kickMe: () => Promise<any>) {
  let resolve: (value: unknown) => void = () => {};
  const promise = new Promise((res) => { resolve = res; });
  const boot = () => { resolve(kickMe()); };
  return { promise, boot };
}

funcitonは、タスクを開始するための引数としてbootablePromise(kickMe:() => Promise<any>)関数を取りkickMeます (この場合はreadFile)。しかし、すぐに開始されるわけではありません。

bootablePromiseいくつかのプロパティを返します

  • promiseタイプのPromise
  • boot型関数の()=>void

promise人生には2つの段階がある

  1. タスクを開始するという約束であること
  2. すでに開始されているタスクを完了する約束であること。

promiseboot()が呼び出されると、最初の状態から 2 番目の状態に遷移します。

bootablePromiseで使用されますprintFiles--

async function printFiles4() {
  const files = await getFilePaths();
  const boots: (() => void)[] = [];
  const set: Set<Promise<{ pidx: number }>> = new Set<Promise<any>>();
  const bootableProms = files.map((file,pidx) => {
    const { promise, boot } = bootablePromise(() => fs.readFile(file, "utf8"));
    boots.push(boot);
    set.add(promise.then(() => ({ pidx })));
    return promise;
  });
  const concurLimit = 2;
  await Promise.all([
    (async () => {                                       // branch 1
      let idx = 0;
      boots.slice(0, concurLimit).forEach((b) => { b(); idx++; });
      while (idx<boots.length) {
        const { pidx } = await Promise.race([...set]);
        set.delete([...set][pidx]);
        boots[idx++]();
      }
    })(),
    (async () => {                                       // branch 2
      for (const p of bootableProms) console.log(await p);
    })(),
  ]);
}

前と同じように、2 つのブランチがあります。

  • ブランチ 1: 同時実行の実行と処理用。
  • ブランチ 2: 印刷用

現在の違いは、concurLimitpromise を同時に実行することは許可されていないことです。

重要な変数は次のとおりです。

  • boots: 対応する遷移の約束を強制するために呼び出す関数の配列。ブランチ 1 でのみ使用されます。
  • set: ランダム アクセス コンテナーには promise が含まれているため、満たされた後は簡単に削除できます。このコンテナはブランチ 1 でのみ使用されます。
  • bootableProms: これらは の最初のとおりの小さな約束ですがset、これは集合ではなく配列であり、配列は決して変更されません。ブランチ 2 でのみ使用されます。

fs.readFile次のような時間がかかるモックで実行します (ファイル名とミリ秒単位の時間)。

const timeTable = {
  "1": 600,
  "2": 500,
  "3": 400,
  "4": 300,
  "5": 200,
  "6": 100,
};

このようなテスト実行時間が見られ、並行性が機能していることを示しています --

[1]0--0.601
[2]0--0.502
[3]0.503--0.904
[4]0.608--0.908
[5]0.905--1.105
[6]0.905--1.005

typescript プレイグラウンド サンドボックスで実行可能ファイルとして利用可能

于 2022-01-17T20:42:27.210 に答える
0

async/await を使用できない場合(IE11、古いパッカーなど)、この再帰関数を試すことができます。非同期呼び出しとして使用fetchしましたが、promise を返す任意の関数を使用できます。

var urlsToGet = ['https://google.com', 'https://yahoo.com'];

fetchOneAtATime(urlsToGet);

function fetchOneAtATime(urls) {
    if (urls.length === 0) {
        return;
    }
    fetch(urls[0]).finally(() => fetchOneAtATime(urls.slice(1)));
}
于 2021-10-27T08:45:48.097 に答える
-1

十分にテストされた (1 週間に数百万回のダウンロード) pifyおよびasyncモジュールを使用します。async モジュールに慣れていない場合は、そのドキュメントを確認することを強くお勧めします。複数の開発者がメソッドの再作成に時間を浪費したり、さらに悪いことに、高次の非同期メソッドがコードを簡素化するときに、保守が困難な非同期コードを作成しているのを見てきました。

const async = require('async')
const fs = require('fs-promise')
const pify = require('pify')

async function getFilePaths() {
    return Promise.resolve([
        './package.json',
        './package-lock.json',
    ]);
}

async function printFiles () {
  const files = await getFilePaths()

  await pify(async.eachSeries)(files, async (file) => {  // <-- run in series
  // await pify(async.each)(files, async (file) => {  // <-- run in parallel
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  })
  console.log('HAMBONE')
}

printFiles().then(() => {
    console.log('HAMBUNNY')
})
// ORDER OF LOGS:
// package.json contents
// package-lock.json contents
// HAMBONE
// HAMBUNNY
```

于 2018-02-04T16:03:47.093 に答える