Używanie async / await z pętlą forEach

Jun 02 2016

Czy są jakieś problemy z używaniem async/ awaitw forEachpętli? Próbuję przeglądać tablicę plików i awaitzawartość każdego pliku.

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()

Ten kod działa, ale czy może coś pójść nie tak? Ktoś mi powiedział, że nie powinieneś używać async/ awaitw takiej funkcji wyższego rzędu, więc chciałem tylko zapytać, czy jest z tym jakiś problem.

Odpowiedzi

2680 Bergi Jun 02 2016 at 02:02

Oczywiście kod działa, ale jestem prawie pewien, że nie robi tego, czego się od niego oczekuje. Po prostu odpala wiele wywołań asynchronicznych, ale po tym printFilesfunkcja natychmiast powraca.

Czytanie po kolei

Jeśli chcesz czytać pliki po kolei, nie możesz użyćforEach . for … ofZamiast tego użyj po prostu nowoczesnej pętli, która awaitbędzie działać zgodnie z oczekiwaniami:

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

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

Czytanie równolegle

Jeśli chcesz czytać pliki równolegle, nie możesz użyćforEach . Każde asyncwywołanie funkcji zwrotnej zwraca obietnicę, ale wyrzucasz je zamiast czekać. Po prostu użyj mapzamiast tego, a możesz czekać na szereg obietnic, które otrzymasz z 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)
  }));
}
274 FranciscoMateo Jun 15 2018 at 18:17

Dzięki ES2018 możesz znacznie uprościć wszystkie powyższe odpowiedzi na:

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

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

Zobacz specyfikację: Propozycja-asynchroniczna-iteracja


10.09.2018: Ta odpowiedź zyskała ostatnio wiele uwagi, zobacz post na blogu Axela Rauschmayera, aby uzyskać więcej informacji na temat iteracji asynchronicznej: ES2018: iteracja asynchroniczna

81 TimothyZorn Mar 27 2018 at 02:48

Zamiast Promise.allw połączeniu z Array.prototype.map(co nie gwarantuje kolejności Promiserozwiązywania problemów), używam Array.prototype.reduce, zaczynając od rozwiązanego Promise:

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());
}
35 AntonioVal Jul 10 2017 at 15:15

Moduł p-iteracji w npm implementuje metody iteracji Array, dzięki czemu można ich używać w bardzo prosty sposób z async / await.

Przykład z twoją sprawą:

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);
  });
})();
32 Matt Mar 22 2018 at 22:11

Oto kilka forEachAsyncprototypów. Pamiętaj, że będziesz ich potrzebować 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));
}

Zauważ , że chociaż możesz uwzględnić to we własnym kodzie, nie powinieneś umieszczać tego w bibliotekach, które dystrybuujesz do innych (aby uniknąć zanieczyszczania ich globalnych).

9 chharvey Feb 23 2018 at 07:47

Oprócz odpowiedzi @ Bergi , chciałbym zaoferować trzecią alternatywę. Jest to bardzo podobne do drugiego przykładu @ Bergi, ale zamiast readFileczekać na każdego z osobna, tworzysz szereg obietnic, na które czekasz na końcu.

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);
}

Zauważ, że funkcja przekazana do .map()nie musi być async, ponieważ fs.readFilei tak zwraca obiekt Promise. Dlatego promisesistnieje tablica obiektów Promise, do których można wysłać Promise.all().

W odpowiedzi @ Bergi konsola może rejestrować zawartość plików w kolejności ich odczytywania. Na przykład, jeśli naprawdę mały plik zakończy czytanie przed naprawdę dużym plikiem, zostanie zarejestrowany jako pierwszy, nawet jeśli mały plik pojawi się po dużym pliku w filestablicy. Jednak w mojej metodzie powyżej masz gwarancję, że konsola zarejestruje pliki w tej samej kolejności, co podana tablica.

7 master_dodo May 27 2019 at 05:08

Rozwiązanie Bergi działa dobrze, gdy fsjest oparte na obietnicy. Można użyć bluebird, fs-extralub fs-promiseza to.

Jednak rozwiązanie dlafs biblioteki natywnej węzła jest następujące:

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;
    }));

Uwaga: require('fs') obowiązkowo przyjmuje funkcję jako trzeci argument, w przeciwnym razie zgłasza błąd:

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
6 HoomanAskari Aug 26 2017 at 17:47

Oba powyższe rozwiązania działają, jednak Antonio wykonuje pracę z mniejszą ilością kodu, oto jak pomogło mi rozwiązać dane z mojej bazy danych, z kilku różnych referencji potomnych, a następnie wepchnąć je wszystkie do tablicy i rozwiązać to w obietnicy w końcu jest Gotowe:

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)))
5 JayEdwards Sep 23 2017 at 06:03

umieszczenie w pliku kilku metod, które będą obsługiwały dane asynchroniczne w kolejności serializacji i nadawały kodowi bardziej konwencjonalny charakter, jest dość bezbolesne. Na przykład:

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;
  };
};

teraz, zakładając, że jest to zapisane w „./myAsync.js”, możesz zrobić coś podobnego do poniższego w sąsiednim pliku:

...
/* 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));
}
5 OliverDixon Apr 17 2020 at 00:18

To rozwiązanie jest również zoptymalizowane pod kątem pamięci, dzięki czemu można je uruchomić na 10000 elementów danych i żądań. Niektóre z innych rozwiązań powodują awarię serwera w przypadku dużych zestawów danych.

W języku 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);
        }
    }

Jak używać?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})
4 LeOn-HanLi Sep 25 2017 at 03:00

Jedno ważne zastrzeżenie : await + for .. ofmetoda i forEach + asyncsposób w rzeczywistości mają inny skutek.

Posiadanie awaitwewnątrz prawdziwej forpętli zapewni, że wszystkie wywołania asynchroniczne będą wykonywane jeden po drugim. A forEach + asyncsposób odpali wszystkie obietnice w tym samym czasie, co jest szybsze, ale czasami przytłaczające ( jeśli wykonasz jakieś zapytanie DB lub odwiedzisz niektóre usługi sieciowe z ograniczeniami wolumenu i nie chcesz uruchamiać 100 000 połączeń naraz).

Możesz również użyć opcji reduce + promise(mniej eleganckie), jeśli nie używasz async/awaiti chcesz mieć pewność, że pliki są czytane jeden po drugim .

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

Lub możesz utworzyć forEachAsync, aby pomóc, ale w zasadzie użyj tej samej pętli for.

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}
4 gsaandy Dec 01 2019 at 23:59

Tylko dodanie do oryginalnej odpowiedzi

  • Składnia równoległego czytania w oryginalnej odpowiedzi jest czasami zagmatwana i trudna do odczytania, może możemy napisać ją w innym podejściu
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);
}

  • Dla operacji sekwencyjnej, a nie tylko dla ... of , będzie również działać normalna pętla 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);
  }
}

4 lukaswilkeer Dec 21 2019 at 08:11

Jak odpowiedź @ Bergi, ale z jedną różnicą.

Promise.all odrzuca wszystkie obietnice, jeśli ktoś zostanie odrzucony.

Więc użyj rekurencji.

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

readFilesQueuejest poza printFilesprzyczyną efekt uboczny * wprowadzony przez console.log, lepiej jest kpić, testować lub szpiegować, więc nie jest fajnie mieć funkcję, która zwraca zawartość (sidenote).

W związku z tym kod można po prostu zaprojektować w ten sposób: trzy oddzielne funkcje, które są „czyste” ** i nie wprowadzają żadnych skutków ubocznych, przetwarzają całą listę i można je łatwo zmodyfikować, aby obsłużyć przypadki zakończone niepowodzeniem.

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)

Przyszła edycja / stan obecny

Węzeł obsługuje oczekiwanie na najwyższym poziomie (nie ma jeszcze wtyczki, nie ma i można ją włączyć za pomocą flag harmonii), jest fajne, ale nie rozwiązuje jednego problemu (strategicznie pracuję tylko na wersjach LTS). Jak zdobyć pliki?

Korzystanie z kompozycji. Biorąc pod uwagę kod, wywołuje we mnie wrażenie, że znajduje się on wewnątrz modułu, więc powinien mieć funkcję, która to robi. Jeśli nie, powinieneś użyć IIFE, aby opakować kod roli w funkcję asynchroniczną, tworząc prosty moduł, który zrobi wszystko za Ciebie, lub możesz iść właściwą drogą, jest kompozycja.

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

Zauważ, że nazwa zmiennej zmienia się z powodu semantyki. Przekazujesz funktor (funkcję, którą może wywołać inna funkcja) i otrzymujesz wskaźnik do pamięci, która zawiera początkowy blok logiki aplikacji.

Ale jeśli nie jest to moduł i musisz wyeksportować logikę?

Zawiń funkcje w funkcję asynchroniczną.

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

Lub zmień nazwy zmiennych, cokolwiek ...


* przez efekt uboczny oznacza każdy efekt kolakteryjny aplikacji, który może zmienić statystykę / zachowanie lub wprowadzić błędy w aplikacji, takie jak IO.

** przez „czysty”, jest w apostrofie, ponieważ funkcje nie są czyste, a kod może być konwergentny do czystej wersji, gdy nie ma wyjścia konsoli, tylko manipulacje danymi.

Poza tym, aby zachować czystość, musisz pracować z monadami, które obsługują efekt uboczny, które są podatne na błędy i traktują ten błąd oddzielnie od aplikacji.

3 Babakness Feb 28 2018 at 11:41

Używając Task, futurize i przechodzącej listy, możesz po prostu to zrobić

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

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

Oto jak to ustawisz

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)

Innym sposobem ustrukturyzowania pożądanego kodu byłoby

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

A może nawet bardziej funkcjonalnie

// 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)

Następnie z funkcji nadrzędnej

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

Jeśli naprawdę chciałeś większej elastyczności w kodowaniu, możesz to zrobić (dla zabawy używam proponowanego operatora Pipe Forward )

import { curry, flip } from 'ramda'

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

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

PS - nie próbowałem tego kodu na konsoli, mogą mieć jakieś literówki ... "prosto freestyle, z góry kopuły!" jak powiedziałyby dzieciaki z lat 90. :-p

3 Beau Mar 13 2019 at 06:31

Obecnie właściwość prototypu Array.forEach nie obsługuje operacji asynchronicznych, ale możemy stworzyć własne wypełnienie poly-fill spełniające nasze potrzeby.

// 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}

I to wszystko! Masz teraz dostępną metodę asynchroniczną forEach dla wszystkich tablic, które są zdefiniowane po tych operacjach.

Przetestujmy to ...

// 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

Moglibyśmy zrobić to samo dla niektórych innych funkcji tablicowych, takich jak 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

... i tak dalej :)

Kilka uwag:

  • Twoja iteratorFunction musi być funkcją asynchroniczną lub obietnicą
  • Żadne tablice utworzone wcześniej Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>nie będą miały tej funkcji
3 PranavKAndro Nov 25 2019 at 03:31

Dziś trafiłem na wiele rozwiązań tego problemu. Uruchamianie funkcji async await w pętli forEach. Budując opakowanie wokół, możemy to osiągnąć.

Bardziej szczegółowe wyjaśnienie, jak to działa wewnętrznie, dla natywnego forEach i dlaczego nie jest w stanie wykonać wywołania funkcji asynchronicznej oraz inne szczegóły dotyczące różnych metod są dostępne w linku tutaj

Wiele sposobów, za pomocą których można to zrobić, a są one następujące:

Metoda 1: użycie opakowania.

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')
           }
         });
     });
    })();

Metoda 2: użycie tego samego, co ogólna funkcja 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');
            }
        })
      });
    };
  }

Stosowanie :

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

Metoda 3:

Korzystanie z Promise.all

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

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

Metoda 4: Tradycyjna for loop lub nowoczesna for loop

// 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);
3 richytong May 21 2020 at 03:57

Możesz użyć Array.prototype.forEach, ale async / await nie jest tak kompatybilny. Dzieje się tak, ponieważ obietnica zwrócona przez wywołanie zwrotne asynchroniczne oczekuje rozwiązania, ale Array.prototype.forEachnie rozwiązuje żadnych obietnic wynikających z wykonania wywołania zwrotnego. Więc możesz użyć forEach, ale będziesz musiał sam poradzić sobie z rozwiązaniem obietnicy.

Oto sposób na odczyt i wydrukowanie każdego pliku w serii przy użyciu 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
}

Oto sposób (nadal używany Array.prototype.forEach) do równoległego drukowania zawartości plików

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)
}
2 jgmjgm Oct 15 2019 at 01:35

Aby zobaczyć, co może się nie udać, wypisz na końcu metody console.log.

Ogólnie rzecz biorąc, mogą się nie udać:

  • Arbitralny porządek.
  • printFiles może zakończyć działanie przed wydrukowaniem plików.
  • Kiepska wydajność.

Nie zawsze są one błędne, ale często występują w standardowych przypadkach użycia.

Generalnie użycie forEach spowoduje wszystkie oprócz ostatniego. Wywoła każdą funkcję bez czekania na funkcję, co oznacza, że ​​mówi wszystkim funkcjom, aby uruchomiły się, a następnie kończą bez czekania na zakończenie funkcji.

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()

To jest przykład w natywnym JS, który zachowuje porządek, zapobiega przedwczesnemu powracaniu funkcji i teoretycznie zachowuje optymalną wydajność.

To będzie:

  • Zainicjuj równoległe odczyty wszystkich plików.
  • Zachowaj kolejność za pomocą map do mapowania nazw plików na obietnice, na które trzeba czekać.
  • Czekaj na każdą obietnicę w kolejności zdefiniowanej przez tablicę.

Dzięki temu rozwiązaniu pierwszy plik zostanie wyświetlony, gdy tylko będzie dostępny, bez konieczności czekania, aż pozostałe będą dostępne.

Będzie również ładować wszystkie pliki w tym samym czasie, zamiast czekać na zakończenie pierwszego przed rozpoczęciem odczytu drugiego pliku.

Jedyną wadą tego i oryginalnej wersji jest to, że jeśli wiele odczytów jest uruchamianych jednocześnie, trudniej jest obsłużyć błędy z powodu większej liczby błędów, które mogą się zdarzyć naraz.

W przypadku wersji, które odczytują plik na raz, zatrzymają się w przypadku niepowodzenia bez marnowania czasu na próby odczytania kolejnych plików. Nawet przy skomplikowanym systemie anulowania może być trudno uniknąć niepowodzenia w przypadku pierwszego pliku, ale odczytanie większości innych plików również jest możliwe.

Wydajność nie zawsze jest przewidywalna. Podczas gdy wiele systemów będzie działać szybciej z równoległym odczytem plików, niektóre będą preferować sekwencyjne. Niektóre są dynamiczne i mogą zmieniać się pod obciążeniem, a optymalizacje, które oferują opóźnienia, nie zawsze zapewniają dobrą przepustowość w trudnych warunkach.

W tym przykładzie nie ma również obsługi błędów. Jeśli coś wymaga od nich pomyślnego pokazania wszystkich lub wcale, nie zrobi tego.

Zalecane jest dogłębne eksperymentowanie z console.log na każdym etapie i fałszywymi rozwiązaniami odczytu plików (zamiast tego losowe opóźnienie). Chociaż wydaje się, że wiele rozwiązań działa tak samo w prostych przypadkach, wszystkie mają subtelne różnice, które wymagają dodatkowej analizy, aby je wycisnąć.

Użyj tej makiety, aby odróżnić rozwiązania:

(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}`);
})();

ScottRudiger Jun 21 2018 at 23:55

Podobnie jak w przypadku Antonio Val p-iteration, alternatywnym modułem npm jest 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();

Alternatywnie async-afma statyczną metodę (log / logAF), która rejestruje wyniki obietnic:

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();

Jednak główną zaletą biblioteki jest to, że można łączyć metody asynchroniczne w łańcuch, aby wykonać coś takiego:

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