forEachループでasync / awaitを使用する


1130

使用に問題があるasync/のawait中にforEachループが?私は、ファイルの配列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れたので、これに問題があるかどうか尋ねたかっただけです。

回答:


2153

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

順番に読む

ファイルを順番に読みたい場合、実際には使用できませんforEachfor … of代わりに、await期待どおりに機能する最新のループを使用してください:

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

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

並行して読む

ファイルを並行して読みたい場合、実際には使用できません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)
  }));
}

33
なぜ機能するのか説明していただけますfor ... of ...か?
Demonbane

84
わかった、なぜだろう... Babelを使用するとasync/ awaitがジェネレーター関数に変換され、使用forEachするということは、各反復に個別のジェネレーター関数があることを意味し、他の関数とは何の関係もありません。そのため、それらは独立して実行されnext()、他のユーザーとのコンテキストはありません。実際、for()反復も1つのジェネレーター関数内にあるため、単純なループも機能します。
デモンベイン

21
@Demonbane:つまり、機能するように設計されているためです:-) すべての制御構造を含むawait現在の関数評価を一時停止します。はい、その点ではジェネレーターと非常によく似ています(これが非同期/待機のポリフィルに使用される理由です)。
Bergi

3
@ arve0実際には、async関数はPromiseexecutorコールバックとはかなり異なりmapますが、どちらの場合もコールバックはpromiseを返します。
ベルギ

5
JSの約束について学びに来たときは、代わりに30分翻訳ラテン語を使用してください。@Bergiを誇りに思ってください;)
-Grenier

189

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

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

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

仕様を参照してください:proposal-async-iteration


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


4
非同期反復について詳しく知りたい人のために、仕様リンクを回答に含めることができれば、賛成です。
saadq

8
それはイテレータに代わり、ファイルの内容であってはならない
FluffyBeing

10
なぜ人々はこの答えを支持していますか?答え、質問、提案を詳しく見てみましょう。の後にofは、配列を返す非同期関数が必要です。それは機能せず、フランシスコは言った。
Yevhenii Herasymchuk

3
@AntonioValに完全に同意します。それは答えではありません。
Yevhenii Herasymchuk

2
私はそれが答えではないことに同意しますが、提案を支持することは、その人気を高め、後でそれを後で使用できるようにする可能性を高める方法です。
Robert Molina

61

(が解決される順序を保証しない)Promise.allと組み合わせて使用​​する代わりに、解決済みのから始めて、を使用します。Array.prototype.mapPromiseArray.prototype.reducePromise

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

  await files.reduce(async (promise, file) => {
    // This line will wait for the last async function to finish.
    // The first iteration uses an already resolved Promise
    // so, it will immediately continue.
    await promise;
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }, Promise.resolve());
}

1
これは完璧に機能します。ありがとうございました。あなたがここで何が起こっているのかを説明できるPromise.resolve()await promise;
parrker9 2018年

1
これはかなりクールです。ファイルが順番に読み込まれ、一度にすべて読み込まれるわけではないと思いますか?
GollyJer

1
@ parrker9 Promise.resolve()は既に解決済みのPromiseオブジェクトを返すため、最初にreduceaがPromise必要です。チェーンawait promise;の最後Promiseが解決するのを待ちます。@GollyJerファイルは、一度に1つずつ順番に処理されます。
ティモシーゾーン

リデュースのとてもクールな使い方、コメントありがとう!コメントで言及されている他のいくつかの方法とは対照的に、これは同期的であることを示します。つまり、ファイルは次々と読み込まれ、並列ではありません(reduce関数の次の反復は前の反復、それは同期でなければなりません)。
シェイ・イザコフ

1
@Shay、あなたは同期的ではなく、順次的を意味します。これはまだ非同期です-他のものがスケジュールされている場合、それらはここの反復の間に実行されます。
ティモシーゾーン

32

npm のp-iterationモジュールは、Array反復メソッドを実装しているため、async / awaitで非常に簡単に使用できます。

あなたのケースの例:

const { forEach } = require('p-iteration');
const fs = require('fs-promise');

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

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

1
JS自体と同じ機能/メソッドを持っているので、私はこれが好きです-私の場合、私が必要としたのsomeではなくforEach。ありがとう!
mikemaccana

25

ここにいくつかのforEachAsyncプロトタイプがあります。あなたはawaitそれらに必要になることに注意してください:

Array.prototype.forEachAsync = async function (fn) {
    for (let t of this) { await fn(t) }
}

Array.prototype.forEachAsyncParallel = async function (fn) {
    await Promise.all(this.map(fn));
}

注意あなたがあなた自身のコードでこれを含むことが、あなたは(彼らのグローバルを汚染を避けるために)あなたが他の人に配布するライブラリでこれを含めるべきではありません。


1
プロトタイプに直接追加することをためらうと思いますが、これは非同期のforEach実装です
DaniOcean

2
名前が将来的に一意になる限り(私が使用するように_forEachAsync)、これは妥当です。また、ボイラープレートコードを大幅に節約できるので、これが最も良い答えだと思います。
mikemaccana

1
@estusそれは他の人のコードを汚染するのを避けるためです。コードが私たちの個人の組織に属していて、グローバルが適切に識別されたファイルにある場合(globals.js良いでしょう)、好きなようにグローバルを追加できます。
mikemaccana

1
@mikemaccanaこれは、一般に受け入れられている悪い習慣を避けるためです。これは事実ですが、まれにしか発生しないファーストパーティのコードのみを使用している限り、これを行うことができます。問題は、サードパーティのライブラリを使用するときに、同じように感じて同じグローバルを変更する他の人がいる可能性があるということです。
Estus Flask

1
@estus確かに。ここで(特に生産的ではない)ディスカッションを保存するために、質問に警告を追加しました。
mikemaccana

6

@Bergiの回答に加えて、3つ目の選択肢を提供したいと思います。@Bergiの2番目の例に非常に似ていますreadFileが、それぞれを個別に待つ代わりに、最後に待つプロミスの配列を作成します。

import fs from 'fs-promise';
async function printFiles () {
  const files = await getFilePaths();

  const promises = files.map((file) => fs.readFile(file, 'utf8'))

  const contents = await Promise.all(promises)

  contents.forEach(console.log);
}

とにかくPromiseオブジェクトを返す.map()のでasync、渡される関数はである必要はありませんfs.readFile。したがってpromises、に送信できるPromiseオブジェクトの配列ですPromise.all()

@Bergiの回答では、コンソールはファイルの内容を読み取った順に記録する場合があります。たとえば、非常に小さいファイルが非常に大きいファイルの前に読み取りを終了すると、小さいファイルが配列内の大きいファイルの後にある場合でも、最初にログに記録されますfiles。ただし、上記の私の方法では、コンソールが提供された配列と同じ順序でファイルを記録することが保証されています。


1
私はあなたが間違っていると確信しています:あなたのメソッドがファイルを順不同で読み取ることもできると確信しています。はい、出力は正しい順序でログに記録されますが(のためawait Promise.all)、ファイルが異なる順序で読み取られた可能性があります。これは、「コンソールがファイルを同じ順序で記録することが保証されていることと矛盾します。読んだ"。
Venryx

1
@Venryx正解です。修正ありがとうございます。回答を更新しました。
chharvey

5

Bergiのソリューションfsは、promiseベースの場合にうまく機能します。あなたは使用することができbluebirdfs-extraまたはfs-promiseこれのために。

ただし、ノードのネイティブfsライブラリの解決策は次のとおりです。

const result = await Promise.all(filePaths
    .map( async filePath => {
      const fileContents = await getAssetFromCache(filePath, async function() {

        // 1. Wrap with Promise    
        // 2. Return the result of the Promise
        return await new Promise((res, rej) => {
          fs.readFile(filePath, 'utf8', function(err, data) {
            if (data) {
              res(data);
            }
          });
        });
      });

      return fileContents;
    }));

注: require('fs')強制的に関数を3番目の引数として受け取り、それ以外の場合はエラーをスローします。

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function

4

上記の両方のソリューションは機能しますが、Antonio'sは少ないコードで作業を行います。ここでは、データベースからのデータを解決し、いくつかの異なる子参照からデータを解決して、それらをすべて配列にプッシュし、結局のところ、promiseで解決しました。完了:

Promise.all(PacksList.map((pack)=>{
    return fireBaseRef.child(pack.folderPath).once('value',(snap)=>{
        snap.forEach( childSnap => {
            const file = childSnap.val()
            file.id = childSnap.key;
            allItems.push( file )
        })
    })
})).then(()=>store.dispatch( actions.allMockupItems(allItems)))

3

シリアル化された順序で非同期データを処理し、コードに従来のフレーバーを与えるファイルにいくつかのメソッドをポップするのはかなり簡単です。例えば:

module.exports = function () {
  var self = this;

  this.each = async (items, fn) => {
    if (items && items.length) {
      await Promise.all(
        items.map(async (item) => {
          await fn(item);
        }));
    }
  };

  this.reduce = async (items, fn, initialValue) => {
    await self.each(
      items, async (item) => {
        initialValue = await fn(initialValue, item);
      });
    return initialValue;
  };
};

今、それが「./myAsync.js」に保存されていると仮定すると、隣接するファイルで以下のようなことができます:

...
/* your server setup here */
...
var MyAsync = require('./myAsync');
var Cat = require('./models/Cat');
var Doje = require('./models/Doje');
var example = async () => {
  var myAsync = new MyAsync();
  var doje = await Doje.findOne({ name: 'Doje', noises: [] }).save();
  var cleanParams = [];

  // FOR EACH EXAMPLE
  await myAsync.each(['bork', 'concern', 'heck'], 
    async (elem) => {
      if (elem !== 'heck') {
        await doje.update({ $push: { 'noises': elem }});
      }
    });

  var cat = await Cat.findOne({ name: 'Nyan' });

  // REDUCE EXAMPLE
  var friendsOfNyanCat = await myAsync.reduce(cat.friends,
    async (catArray, friendId) => {
      var friend = await Friend.findById(friendId);
      if (friend.name !== 'Long cat') {
        catArray.push(friend.name);
      }
    }, []);
  // Assuming Long Cat was a friend of Nyan Cat...
  assert(friendsOfNyanCat.length === (cat.friends.length - 1));
}

2
小さな補遺、あなたの待機/非同期をtry / catchブロックでラップすることを忘れないでください!!
ジェイエドワーズ

3

@Bergiの応答に似ていますが、1つだけ違いがあります。

Promise.all 1つが拒否された場合、すべての約束を拒否します。

したがって、再帰を使用します。

const readFilesQueue = async (files, index = 0) {
    const contents = await fs.readFile(files[index], 'utf8')
    console.log(contents)

    return files.length <= index
        ? readFilesQueue(files, ++index)
        : files

}

const printFiles async = () => {
    const files = await getFilePaths();
    const printContents = await readFilesQueue(files)

    return printContents
}

printFiles()

PS

readFilesQueueprintFilesによって導入された副作用* の原因ではconsole.logないので、モック、テスト、またはスパイする方が良いので、コンテンツを返す関数(サイドノート)を持つのはクールではありません。

したがって、コードは次のように簡単に設計できます。「純粋」**で副作用のない3つの分離された関数、リスト全体を処理し、失敗したケースを処理するように簡単に変更できます。

const files = await getFilesPath()

const printFile = async (file) => {
    const content = await fs.readFile(file, 'utf8')
    console.log(content)
}

const readFiles = async = (files, index = 0) => {
    await printFile(files[index])

    return files.lengh <= index
        ? readFiles(files, ++index)
        : files
}

readFiles(files)

今後の編集/現状

ノードはトップレベルの待機をサポートしています(これにはプラグインがなく、ハーモニーフラグを介して有効にできません)。これはクールですが、1つの問題を解決しません(戦略的にはLTSバージョンでのみ動作します)。ファイルの入手方法は?

構成を使用します。コードを考えると、これはモジュールの中にあるという感覚を引き起こします。そのため、それを行う関数が必要です。そうでない場合は、IIFEを使用して役割コードを非同期関数にラップし、すべてを実行する単純なモジュールを作成するか、正しい方法で構成することができます。

// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)

セマンティクスのために変数の名前が変わることに注意してください。ファンクタ(別の関数から呼び出すことができる関数)を渡し、アプリケーションのロジックの初期ブロックを含むメモリ上のポインタを受け取ります。

しかし、モジュールではなく、ロジックをエクスポートする必要がある場合はどうでしょうか。

関数を非同期関数でラップします。

export const readFilesQueue = async () => {
    // ... to code goes here
}

または、変数の名前を変更します...


* 副作用とは、アプリケーションの状態/動作またはIOなどのアプリケーションのバグを変更する可能性のあるアプリケーションのすべての協調効果を意味します。

** 「純粋」とは、それがアポストロフィになっていることを意味します。これは、関数が純粋ではないため、コンソール出力がなく、データ操作のみの場合に、コードを純粋なバージョンに収束できるためです。

これとは別に、純粋に言うと、副作用を処理し、エラーが発生しやすく、そのエラーをアプリケーションとは別に処理するモナドを使用する必要があります。


2

重要な警告の 1つは、await + for .. of方法とforEach + async方法には実際には異なる効果があることです。

持つawait本当の内側にforループすることを確認します、すべての非同期呼び出しが1つずつ実行します。そして、forEach + async方法はすべてのプロミスを同時に起動しますが、これはより高速ですが、場合によっては圧倒されます(DBクエリを実行するか、ボリューム制限のあるWebサービスにアクセスし、一度に100,000コールを起動したくない場合)。

使用reduce + promiseせずasync/await、ファイルが次々と読み込まれるようにしたい場合は(あまりエレガントでない)を使用することもできます。

files.reduce((lastPromise, file) => 
 lastPromise.then(() => 
   fs.readFile(file, 'utf8')
 ), Promise.resolve()
)

または、助けるためにforEachAsyncを作成することもできますが、基本的に同じforループの基礎を使用します。

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}

見ていては、ループ内のために表示されないようにArray.prototypeをとのObject.prototype上のJavaScriptでメソッドを定義する方法を。また、おそらくネイティブと同じイテレーションを使用し、反復性forEachに依存する代わりにインデックスにアクセスし、インデックスをコールバックに渡す必要があります。
Bergi

Array.prototype.reduce非同期機能を使用する方法で使用できます。私は私の回答に例を示しました:stackoverflow.com/a/49499491/2537258
Timothy Zorn

2

Task、futurize、および走査可能なリストを使用して、簡単に行うことができます

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

  List(files).traverse( Task.of, f => readFile( f, 'utf-8'))
    .fork( console.error, console.log)
}

これを設定する方法は次のとおりです

import fs from 'fs';
import { futurize } from 'futurize';
import Task from 'data.task';
import { List } from 'immutable-ext';

const future = futurizeP(Task)
const readFile = future(fs.readFile)

目的のコードを構造化する別の方法は、

const printFiles = files => 
  List(files).traverse( Task.of, fn => readFile( fn, 'utf-8'))
    .fork( console.error, console.log)

またはおそらくさらに機能指向

// 90% of encodings are utf-8, making that use case super easy is prudent

// handy-library.js
export const readFile = f =>
  future(fs.readFile)( f, 'utf-8' )

export const arrayToTaskList = list => taskFn => 
  List(files).traverse( Task.of, taskFn ) 

export const readFiles = files =>
  arrayToTaskList( files, readFile )

export const printFiles = files => 
  readFiles(files).fork( console.error, console.log)

次に、親関数から

async function main() {
  /* awesome code with side-effects before */
  printFiles( await getFiles() );
  /* awesome code with side-effects after */
}

エンコーディングの柔軟性を高めたい場合は、これを行うことができます(おもしろいので、提案されているパイプ転送演算子を使用しています)。

import { curry, flip } from 'ramda'

export const readFile = fs.readFile 
  |> future,
  |> curry,
  |> flip

export const readFileUtf8 = readFile('utf-8')

PS-コンソールでこのコードを試さなかったので、タイプミスがあるかもしれません...「ドームの上部から外れた直線のフリースタイル!」90年代の子供たちが言うように。:-p


2

現在、Array.forEachプロトタイププロパティは非同期操作をサポートしていませんが、ニーズを満たすために独自のポリフィルを作成できます。

// Example of asyncForEach Array poly-fill for NodeJs
// file: asyncForEach.js
// Define asynForEach function 
async function asyncForEach(iteratorFunction){
  let indexer = 0
  for(let data of this){
    await iteratorFunction(data, indexer)
    indexer++
  }
}
// Append it as an Array prototype property
Array.prototype.asyncForEach = asyncForEach
module.exports = {Array}

以上です!これで、これらの操作の後に定義されたすべての配列で使用できる非同期のforEachメソッドがあります。

テストしてみましょう...

// Nodejs style
// file: someOtherFile.js

const readline = require('readline')
Array = require('./asyncForEach').Array
const log = console.log

// Create a stream interface
function createReader(options={prompt: '>'}){
  return readline.createInterface({
    input: process.stdin
    ,output: process.stdout
    ,prompt: options.prompt !== undefined ? options.prompt : '>'
  })
}
// Create a cli stream reader
async function getUserIn(question, options={prompt:'>'}){
  log(question)
  let reader = createReader(options)
  return new Promise((res)=>{
    reader.on('line', (answer)=>{
      process.stdout.cursorTo(0, 0)
      process.stdout.clearScreenDown()
      reader.close()
      res(answer)
    })
  })
}

let questions = [
  `What's your name`
  ,`What's your favorite programming language`
  ,`What's your favorite async function`
]
let responses = {}

async function getResponses(){
// Notice we have to prepend await before calling the async Array function
// in order for it to function as expected
  await questions.asyncForEach(async function(question, index){
    let answer = await getUserIn(question)
    responses[question] = answer
  })
}

async function main(){
  await getResponses()
  log(responses)
}
main()
// Should prompt user for an answer to each question and then 
// log each question and answer as an object to the terminal

mapのような他のいくつかの配列関数についても同じことができます...

async function asyncMap(iteratorFunction){
  let newMap = []
  let indexer = 0
  for(let data of this){
    newMap[indexer] = await iteratorFunction(data, indexer, this)
    indexer++
  }
  return newMap
}

Array.prototype.asyncMap = asyncMap

... 等々 :)

注意すべき点:

  • iteratorFunctionは非同期関数またはpromiseである必要があります
  • 以前Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>に作成されたアレイには、この機能はありません。

2

元の答えに追加するだけ

  • 元の回答の並列読み取り構文は、時々混乱して読みにくい場合があります。別の方法で書くことができるかもしれません
async function printFiles() {
  const files = await getFilePaths();
  const fileReadPromises = [];

  const readAndLogFile = async filePath => {
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
    return contents;
  };

  files.forEach(file => {
    fileReadPromises.push(readAndLogFile(file));
  });

  await Promise.all(fileReadPromises);
}
  • for ... ofだけでなく、順次操作の場合、通常のforループも機能します
async function printFiles() {
  const files = await getFilePaths();

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
  }
}

1

どのようにして問題が発生するかを確認するには、メソッドの最後にconsole.logを出力します。

一般的にうまくいかないことがあるもの:

  • 任意の順序。
  • printFilesは、ファイルを印刷する前に実行を終了できます。
  • 業績不振。

これらは常に間違っているわけではありませんが、標準的なユースケースで頻繁に見られます。

通常、forEachを使用すると、最後以外のすべてになります。関数を待たずに各関数を呼び出します。つまり、すべての関数が開始するように指示し、関数が終了するのを待たずに終了します。

import fs from 'fs-promise'

async function printFiles () {
  const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'))

  for(const file of files)
    console.log(await file)
}

printFiles()

これは、順序を保持し、関数が時期尚早に戻るのを防ぎ、理論的には最適なパフォーマンスを維持するネイティブJSの例です。

この意志:

  • すべてのファイル読み取りを開始して、並行して実行します。
  • mapを使用して順序を維持し、ファイル名を待機する約束にマップします。
  • 配列で定義された順序で各プロミスを待ちます。

このソリューションでは、最初のファイルは、他のファイルが最初に利用可能になるのを待たずに、利用可能になるとすぐに表示されます。

また、2番目のファイルの読み取りを開始する前に、最初のファイルが完了するのを待つ必要がなく、すべてのファイルを同時にロードします。

これと元のバージョンの唯一の欠点は、一度に複数の読み取りが開始された場合、一度に発生する可能性のあるエラーが増えるため、エラーの処理がより困難になることです。

一度にファイルを読み取るバージョンでは、ファイルを読み取ろうとする時間を無駄にすることなく、失敗時に停止します。精巧なキャンセルシステムがあっても、最初のファイルで失敗するのを避けるのは難しいかもしれませんが、他のほとんどのファイルもすでに読み取っています。

パフォーマンスは常に予測できるとは限りません。多くのシステムは並列ファイル読み取りでより高速になりますが、シーケンシャルを好むシステムもあります。一部は動的であり、負荷がかかるとシフトする可能性があります。レイテンシを提供する最適化では、激しい競合下で常に良好なスループットが得られるとは限りません。

その例にはエラー処理もありません。何かがそれらをすべて正常に表示するか、まったく表示しないようにする必要がある場合、それはそれを行いません。

各段階でconsole.logを使用して詳細な実験を行うことをお勧めします。偽のファイル読み取りソリューション(ランダムな遅延)。多くの解決策は単純なケースでも同じことをするように見えますが、すべては微妙な違いがあり、絞り出すために特別な精査が必要です。

このモックを使用して、ソリューション間の違いを確認します。

(async () => {
  const start = +new Date();
  const mock = () => {
    return {
      fs: {readFile: file => new Promise((resolve, reject) => {
        // Instead of this just make three files and try each timing arrangement.
        // IE, all same, [100, 200, 300], [300, 200, 100], [100, 300, 200], etc.
        const time = Math.round(100 + Math.random() * 4900);
        console.log(`Read of ${file} started at ${new Date() - start} and will take ${time}ms.`)
        setTimeout(() => {
          // Bonus material here if random reject instead.
          console.log(`Read of ${file} finished, resolving promise at ${new Date() - start}.`);
          resolve(file);
        }, time);
      })},
      console: {log: file => console.log(`Console Log of ${file} finished at ${new Date() - start}.`)},
      getFilePaths: () => ['A', 'B', 'C', 'D', 'E']
    };
  };

  const printFiles = (({fs, console, getFilePaths}) => {
    return async function() {
      const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'));

      for(const file of files)
        console.log(await file);
    };
  })(mock());

  console.log(`Running at ${new Date() - start}`);
  await printFiles();
  console.log(`Finished running at ${new Date() - start}`);
})();

1

今日、私はこのための複数のソリューションに出くわしました。forEachループで非同期待機関数を実行します。周りにラッパーを構築することで、これを実現できます。

ネイティブの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);

方法1と2は、Promise.all使用されるべきであった、単に正しくない実装です。これらは、多くのエッジケースを考慮に入れていません。
Bergi、

@Bergi:有効なコメントをありがとう、方法1と2が正しくない理由を教えてください。それも目的を果たします。これは非常にうまく機能します。これは、これらの方法はすべて、1つを選択することを決定できる状況に基づいて可能であるということです。私は同じための実行例を持っています。
PranavKAndro

空の配列では失敗し、エラー処理はなく、おそらくさらに問題があります。車輪を再発明しないでください。だけを使用してくださいPromise.all
ベルギ

それが不可能である特定の状況では、それは役に立ちます。また、エラー処理はデフォルトでforEach APIによって行われるため、問題はありません。その世話をした!
PranavKAndro

いいえ、Promise.allできませんがasync/ である条件はありませんawait。いいえ、forEach絶対にプロミスエラーは処理しません。
ベルギ

1

このソリューションはメモリ最適化もされているため、10,000のデータ項目とリクエストで実行できます。ここにある他のソリューションの一部は、大きなデータセットでサーバーをクラッシュさせます。

TypeScriptの場合:

export async function asyncForEach<T>(array: Array<T>, callback: (item: T, index: number) => void) {
        for (let index = 0; index < array.length; index++) {
            await callback(array[index], index);
        }
    }

使い方?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})

1

使用できます Array.prototype.forEachが、async / awaitはそれほど互換性がありません。これは、非同期コールバックから返されるpromiseは解決されることを期待しArray.prototype.forEachていますが、コールバックの実行からのpromiseは解決されないためです。したがって、forEachを使用できますが、promise解決を自分で処理する必要があります。

以下は、各ファイルを連続して読み取り、印刷する方法です。 Array.prototype.forEach

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

  let promiseChain = Promise.resolve()
  files.forEach((file) => {
    promiseChain = promiseChain.then(() => {
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    })
  })
  await promiseChain
}

これは、Array.prototype.forEachファイルの内容を並行して印刷する方法(まだを使用)です

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

  const promises = []
  files.forEach((file) => {
    promises.push(
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    )
  })
  await Promise.all(promises)
}

最初のシナリオは、セリエで実行する必要があるループに理想的で、次の用途には使用できません
Mark Odey

0

Antonio Valと同様にp-iteration、代替のnpmモジュールはasync-af次のとおりです。

const AsyncAF = require('async-af');
const fs = require('fs-promise');

function printFiles() {
  // since AsyncAF accepts promises or non-promises, there's no need to await here
  const files = getFilePaths();

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

printFiles();

または、async-afpromiseの結果をログに記録する静的メソッド(log / logAF)があります。

const AsyncAF = require('async-af');
const fs = require('fs-promise');

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

  AsyncAF(files).forEach(file => {
    AsyncAF.log(fs.readFile(file, 'utf8'));
  });
}

printFiles();

ただし、ライブラリの主な利点は、非同期メソッドをチェーンして次のようなことができることです。

const aaf = require('async-af');
const fs = require('fs-promise');

const printFiles = () => aaf(getFilePaths())
  .map(file => fs.readFile(file, 'utf8'))
  .forEach(file => aaf.log(file));

printFiles();

async-af


-3

私は十分にテストされた(毎週数百万回のダウンロード)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
```


これは間違った方向への一歩です。これが、コールバックの地獄で最新のJS時代に巻き込まれるのを助けるために作成したマッピングガイドです:github.com/jmjpro/async-package-to-async-await/blob/master/…
jbustamovej 2018

ここを見るとわかるように、私はasync libの代わりにasync / awaitを使用することに興味があり、それを利用できます。今、それぞれに時間と場所があると思います。async lib == "callback hell"とasync / await == "modern JS era"だとは思いません。imo、async lib> async / awaitの場合:1.複雑なフロー(例:キュー、貨物、物事が複雑になった場合でも自動)2.並行性3.サポートする配列/オブジェクト/反復可能オブジェクト4.エラー処理
Zachary Ryan Smith
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.