Używanie async / await z pętlą forEach
Czy są jakieś problemy z używaniem async
/ await
w forEach
pętli? Próbuję przeglądać tablicę plików i await
zawartość 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
/ await
w takiej funkcji wyższego rzędu, więc chciałem tylko zapytać, czy jest z tym jakiś problem.
Odpowiedzi
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 printFiles
funkcja natychmiast powraca.
Czytanie po kolei
Jeśli chcesz czytać pliki po kolei, nie możesz użyćforEach
. for … of
Zamiast tego użyj po prostu nowoczesnej pętli, która await
bę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 async
wywołanie funkcji zwrotnej zwraca obietnicę, ale wyrzucasz je zamiast czekać. Po prostu użyj map
zamiast 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)
}));
}
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
Zamiast Promise.all
w połączeniu z Array.prototype.map
(co nie gwarantuje kolejności Promise
rozwią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());
}
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);
});
})();
Oto kilka forEachAsync
prototypó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).
Oprócz odpowiedzi @ Bergi , chciałbym zaoferować trzecią alternatywę. Jest to bardzo podobne do drugiego przykładu @ Bergi, ale zamiast readFile
czekać 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.readFile
i tak zwraca obiekt Promise. Dlatego promises
istnieje 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 files
tablicy. Jednak w mojej metodzie powyżej masz gwarancję, że konsola zarejestruje pliki w tej samej kolejności, co podana tablica.
Rozwiązanie Bergi działa dobrze, gdy fs
jest oparte na obietnicy. Można użyć bluebird
, fs-extra
lub fs-promise
za 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
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)))
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));
}
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 ...
})
Jedno ważne zastrzeżenie : await + for .. of
metoda i forEach + async
sposób w rzeczywistości mają inny skutek.
Posiadanie await
wewnątrz prawdziwej for
pętli zapewni, że wszystkie wywołania asynchroniczne będą wykonywane jeden po drugim. A forEach + async
sposó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/await
i 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);
}
}
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);
}
}
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
readFilesQueue
jest poza printFiles
przyczyną 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.
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
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
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ąć.
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);
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.forEach
nie 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)
}
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}`);
})();
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-af
ma 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();