Używanie async / wait z pętlą forEach
Czy są jakieś problemy z używaniem async/await
W pętli forEach
? Próbuję zapętlić tablicę plików i await
Na zawartości 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 coś może pójść nie tak? Ktoś mi powiedział, że nie powinieneś używać async/await
w funkcji wyższego rzędu, takiej jak ta, więc chciałem zapytać, czy nie ma z tym problemu.
12 answers
Jasne, że kod działa, ale jestem prawie pewien, że nie robi tego, czego oczekujesz. Po prostu odpala wiele wywołań asynchronicznych, ale funkcja printFiles
natychmiast powraca po tym.
Jeśli chcesz czytać pliki w kolejności, nie możesz użyć forEach
w rzeczy samej. Po prostu użyj nowoczesnej pętli for … of
, w której 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);
}
}
Jeśli chcesz czytać pliki równolegle, nie możesz użyć forEach
w rzeczy samej. Każdy z async
wywołania funkcji zwrotnej zwracają obietnicę, ale wyrzucasz je zamiast czekać na nie. Po prostu użyj map
zamiast tego, a możesz czekać na tablicę 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)
}));
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-01 21:50:14
Z ES2018, jesteś w stanie znacznie uprościć wszystkie powyższe odpowiedzi na:
async function printFiles () {
const files = await getFilePaths()
for await (const file of fs.readFile(file, 'utf8')) {
console.log(contents)
}
}
Patrz spec: https://github.com/tc39/proposal-async-iteration
2018-09-10: ta odpowiedź cieszy się ostatnio dużym zainteresowaniem, więcej informacji na temat iteracji asynchronicznej można znaleźć w poście Axela Rauschmayera: http://2ality.com/2016/10/asynchronous-iteration.html
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-10 20:58:29
Dla mnie używanie Promise.all()
z map()
jest trochę trudne do zrozumienia i słowne, ale jeśli chcesz to zrobić w zwykłym JS, to chyba najlepszy strzał.
Jeśli nie masz nic przeciwko dodaniu modułu, zaimplementowałem metody iteracji tablicy, aby można było ich używać w bardzo prosty sposób z async/wait.
Przykład z twoim przypadkiem:
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);
});
}
printFiles()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-16 00:03:01
Oto kilka prototypów foreach async:
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));
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-22 15:11:16
Zamiast Promise.all
w połączeniu z Array.prototype.map
(co nie gwarantuje kolejności, w jakiej Promise
S są rozwiązane), 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());
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-17 15:01:32
Oba powyższe rozwiązania działają, jednak Antonio robi to 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 wcisnąć je wszystkie do tablicy i rozwiązać je w obietnicy po wszystkim jest zrobione: {]}
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)))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-26 10:47:21
To dość bezbolesne wstawić kilka metod do pliku, który będzie obsługiwał dane asynchroniczne w serializowanej kolejności i nada bardziej konwencjonalny smak twojemu kodowi. 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;
};
};
/ Align = "left" / / myAsyncjs ' 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));
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-26 09:07:26
Używając zadania, futurize i listy, możesz po prostu 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 uporządkowania pożądanego kodu jest
const printFiles = files =>
List(files).traverse( Task.of, fn => readFile( fn, 'utf-8'))
.fork( console.error, console.log)
A może nawet bardziej funkcjonalnie zorientowane
// 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 rodzica
async function main() {
/* awesome code with side-effects before */
printFiles( await getFiles() );
/* awesome code with side-effects after */
}
Jeśli naprawdę chcesz 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 - ja nie spróbuj tego kodu na konsoli, może mieć jakieś literówki... "prosty freestyle, poza kopułą!"jak powiedzieliby dzieciaki z Lat 90. :- p
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-03 22:51:49
Oprócz @ Bergi ' s answer, chciałbym zaproponować trzecią alternatywę. Jest to bardzo podobne do drugiego przykładu @ Bergi, ale zamiast czekać na każdą readFile
z osobna, tworzysz tablicę obietnic, z których każda czeka 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 przekazywana do .map()
nie musi być async
, ponieważ fs.readFile
i tak zwraca obiekt Promise. Dlatego promises
jest tablicą obiektów obietnicy, którą można wysłać do Promise.all()
.
W odpowiedzi @Bergi, konsola może rejestrować zawartość pliku nie w porządku. Na przykład, jeśli naprawdę mały plik zakończy odczyt przed naprawdę dużym plikiem, zostanie on najpierw zalogowany, nawet jeśli mały plik pojawi się po dużym pliku w tablicy files
. Jednak w mojej metodzie powyżej masz gwarancję, że konsola będzie logować pliki w tej samej kolejności, w jakiej są odczytywane.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-21 21:21:23
Jedno ważne zastrzeżenie jest następujące: metoda await + for .. of
i Metoda forEach + async
faktycznie mają różne efekty.
Posiadanie await
wewnątrz prawdziwej pętli for
upewni się, że wszystkie wywołania asynchroniczne są wykonywane jeden po drugim. I sposób forEach + async
odpali wszystkie obietnice w tym samym czasie, co jest szybsze, ale czasami przytłoczone (jeśli wykonasz jakieś zapytanie DB lub odwiedzisz niektóre Usługi internetowe z ograniczeniami wolumenu i nie chcesz odpalić 100 000 połączeń na raz).
Możesz również użyć reduce + promise
(mniej elegancki), jeśli nie używasz async/await
i chcesz mieć pewność, że pliki są odczytywane jeden po drugim.
files.reduce((lastPromise, file) =>
lastPromise.then(() =>
fs.readFile(file, 'utf8')
), Promise.resolve()
)
Lub możesz utworzyć forEachAsync, aby pomóc, ale zasadniczo użyć tego samego dla loop based.
Array.prototype.forEachAsync = async function(cb){
for(let x of this){
await cb(x);
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-24 20:00:43
Podobne do Antonio Val ' s 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
posiada 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ć asynchroniczne metody, aby zrobić 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();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-21 16:55:47
Użyłbym dobrze przetestowanych (miliony pobrań tygodniowo) pify i async modułów. Jeśli nie jesteś zaznajomiony z modułem asynchronicznym, Gorąco polecam zapoznanie się z jego dokumentami . Widziałem wielu programistów marnujących czas na odtwarzanie swoich metod lub, co gorsza, na tworzenie trudnego do utrzymania kodu asynchronicznego, gdy metody asynchroniczne wyższego rzędu upraszczałyby kod.
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
```
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-04 16:03:47