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 coś może pójść nie tak? Kazałem mi powiedzieć, ż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.
for ... of ...
działa?async
/await
generuje funkcję generatora, a użycieforEach
oznacza, że każda iteracja ma osobną funkcję generatora, która nie ma nic wspólnego z innymi. więc będą wykonywane niezależnie i nie mają kontekstunext()
z innymi. W rzeczywistości prostafor()
pętla działa również, ponieważ iteracje są również w funkcji pojedynczego generatora.await
zawiesza bieżącą ocenę funkcji , w tym wszystkie struktury kontrolne. Tak, pod tym względem jest dość podobny do generatorów (dlatego są one używane do asynchronizacji / oczekiwania na polifill).async
funkcja jest całkiem inna niżPromise
wywołanie zwrotne executora, ale tak,map
callback zwraca obietnicę w obu przypadkach.Dzięki ES2018 możesz znacznie uprościć wszystkie powyższe odpowiedzi na:
Patrz spec: iteracja-propozycja-asynchronizacja
2018-09-10: Ta odpowiedź cieszy się ostatnio dużym zainteresowaniem, więcej informacji na temat iteracji asynchronicznej można znaleźć na blogu Axela Rauschmayera: ES2018: iteracja asynchroniczna
źródło
of
powinna być funkcja asynchroniczna, która zwróci tablicę. To nie działa i Francisco powiedział;Zamiast
Promise.all
w połączeniu zArray.prototype.map
(co nie gwarantuje kolejności, w którejPromise
s są rozwiązywane), używamArray.prototype.reduce
, zaczynając od rozwiązanejPromise
:źródło
Promise.resolve()
iawait promise;
?Promise.resolve()
zwraca już rozwiązanyPromise
obiekt, więcreduce
musi onPromise
zaczynać.await promise;
będzie czekać naPromise
rozstrzygnięcie ostatniego w łańcuchu. @GollyJer Pliki będą przetwarzane sekwencyjnie, po jednym na raz.Moduł p-iteracyjny na npm implementuje metody iteracji Array, dzięki czemu można ich używać w bardzo prosty sposób za pomocą asynchronizacji / oczekiwania.
Przykład z twoją sprawą:
źródło
some
raczej tego potrzebowałemforEach
. Dzięki!Oto kilka
forEachAsync
prototypów. Pamiętaj, że musiszawait
im:Pamiętaj , że chociaż możesz to uwzględnić we własnym kodzie, nie powinieneś umieszczać tego w bibliotekach, które dystrybuujesz innym (aby uniknąć zanieczyszczenia ich globali).
źródło
_forEachAsync
), jest to uzasadnione. Myślę też, że jest to najładniejsza odpowiedź, ponieważ pozwala zaoszczędzić sporo kodu z podstawowymi informacjami.globals.js
byłoby dobrze), możemy dodawać globale według własnego uznania.Oprócz odpowiedzi @ Bergi chciałbym zaoferować trzecią alternatywę. Jest bardzo podobny do drugiego przykładu @ Bergi, ale zamiast
readFile
czekać na każdego z osobna, tworzysz szereg obietnic, na które czekasz na końcu.Zauważ, że przekazana funkcja
.map()
nie musi byćasync
, ponieważfs.readFile
i tak zwraca obiekt Promise. Dlategopromises
jest tablica obiektów Promise, do których można wysłaćPromise.all()
.W odpowiedzi @ Bergi konsola może rejestrować zawartość pliku w kolejności, w jakiej są czytane. Na przykład, jeśli naprawdę mały plik zakończy czytanie przed naprawdę dużym plikiem, zostanie najpierw zalogowany, nawet jeśli mały plik pojawia się za dużym plikiem w
files
tablicy. Jednak w powyższej metodzie masz gwarancję, że konsola będzie rejestrować pliki w tej samej kolejności, co podana tablica.źródło
await Promise.all
), ale pliki mogły zostać odczytane w innej kolejności, co jest sprzeczne z Twoim stwierdzeniem „masz gwarancję, że konsola będzie logować pliki w tej samej kolejności, w jakiej są czytać".Rozwiązanie Bergi działa dobrze, gdy
fs
jest oparte na obietnicach. Można użyćbluebird
,fs-extra
lubfs-promise
za to.Jednak rozwiązanie dla natywnej
fs
biblioteki węzłów jest następujące:Uwaga:
require('fs')
obowiązkowo przyjmuje funkcję jako trzeci argument, w przeciwnym razie generuje błąd:źródło
Oba powyższe rozwiązania działają, jednak Antonio wykonuje pracę z mniejszym kodem, oto jak pomógł 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 gotowy:
źródło
dość łatwo jest wstawić kilka metod w pliku, który będzie obsługiwał asynchroniczne dane w serializowanej kolejności i nada Twojemu kodowi bardziej konwencjonalny smak. Na przykład:
teraz, zakładając, że jest zapisany w „./myAsync.js”, możesz zrobić coś podobnego do poniższego w sąsiednim pliku:
źródło
Jak odpowiedź @ Bergi, ale z jedną różnicą.
Promise.all
odrzuca wszystkie obietnice, jeśli ktoś zostanie odrzucony.Użyj rekurencji.
PS
readFilesQueue
występuje pozaprintFiles
przyczyną działania niepożądanego * wprowadzonego przezconsole.log
, lepiej kpić, testować i szpiegować, więc nie jest fajnie mieć funkcję, która zwraca treść (sidenote).Dlatego 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 mogą być łatwo modyfikowane w celu obsługi nieudanych przypadków.
Przyszła edycja / aktualny stan
Węzeł obsługuje najwyższy poziom oczekiwania (nie ma jeszcze wtyczki, nie będzie i może być włączony za pomocą flag harmonii), jest fajny, ale nie rozwiązuje jednego problemu (strategicznie pracuję tylko na wersjach LTS). Jak zdobyć pliki?
Używanie kompozycji. Biorąc pod uwagę kod, sprawia mi wrażenie, że jest w module, więc powinien mieć jakąś funkcję, aby to zrobić. Jeśli nie, powinieneś użyć IIFE do zawinięcia kodu roli w funkcję asynchroniczną, tworząc prosty moduł, który zrobi wszystko za Ciebie, lub możesz iść właściwą drogą, jest tam kompozycja.
Zauważ, że nazwa zmiennej zmienia się z powodu semantyki. Zdajesz funktor (funkcję, którą można wywołać za pomocą innej funkcji) i otrzymuje wskaźnik pamięci, który zawiera początkowy blok logiki aplikacji.
Ale jeśli nie jest to moduł i musisz wyeksportować logikę?
Zawiń funkcje w funkcję asynchroniczną.
Lub zmień nazwy zmiennych, cokolwiek ...
*
przez efekt uboczny oznacza każdy efekt bakteryjny aplikacji, który może zmienić błędy statyczne / zachowanie lub introuce w aplikacji, takie jak IO.**
przez „czysty”, jest w apostrofie, ponieważ funkcje nie są czyste, a kod można konwertować na czystą wersję, gdy nie ma wyjścia konsoli, tylko manipulacje danymi.Poza tym, aby być czystym, musisz pracować z monadami, które obsługują efekt uboczny, które są podatne na błędy i traktuje ten błąd osobno od aplikacji.
źródło
Ważnym zastrzeżeniem jest to, że
await + for .. of
metoda iforEach + async
sposób mają inny wpływ.Posiadanie
await
wewnątrz prawdziwejfor
pętli zapewni, że wszystkie wywołania asynchroniczne będą wykonywane jeden po drugim. AforEach + async
sposób wyzwoli wszystkie obietnice w tym samym czasie, co jest szybsze, ale czasem przytłoczone ( jeśli wykonasz zapytanie DB lub odwiedzisz niektóre serwisy internetowe z ograniczeniami głośności i nie chcesz uruchamiać 100 000 połączeń jednocześnie).Możesz także użyć
reduce + promise
(mniej eleganckiego), jeśli nie używaszasync/await
i chcesz mieć pewność, że pliki będą odczytywane jeden po drugim .Możesz też utworzyć forEachAsync, aby pomóc, ale w zasadzie użyj tego samego dla pętli bazowej.
źródło
forEach
- uzyskując dostęp do indeksów zamiast polegać na iteracji - i przekazać indeks do wywołania zwrotnego.Array.prototype.reduce
w sposób wykorzystujący funkcję asynchroniczną. Pokazałem przykład w mojej odpowiedzi: stackoverflow.com/a/49499491/2537258Korzystając z Task, futurize i listy, którą możesz przejść, możesz po prostu to zrobić
Oto jak to skonfigurować
Innym sposobem na uporządkowanie pożądanego kodu jest
A może nawet bardziej funkcjonalnie
Następnie z funkcji nadrzędnej
Jeśli naprawdę chciałeś większej elastyczności kodowania, możesz to zrobić (dla zabawy korzystam z proponowanego operatora Pipe Forward )
PS - nie wypróbowałem tego kodu na konsoli, może mieć literówki ... „prosto freestyle, z góry kopuły!” jak powiedzieliby dzieci z lat 90. :-p
źródło
Obecnie prototypowa właściwość Array.forEach nie obsługuje operacji asynchronicznych, ale możemy stworzyć własne wypełnienie spełniające nasze potrzeby.
I to wszystko! Masz teraz asynchroniczną metodę forEach dostępną dla dowolnych tablic, które zostały zdefiniowane po nich dla operacji.
Przetestujmy to ...
Możemy zrobić to samo dla niektórych innych funkcji tablicy, takich jak map ...
... i tak dalej :)
Kilka rzeczy do zapamiętania:
Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>
nie będą miały tej funkcjiźródło
Właśnie dodając do oryginalnej odpowiedzi
źródło
Dzisiaj natknąłem się na wiele rozwiązań w tym zakresie. Uruchomienie asynchronizacji oczekuje na funkcje w pętli forEach. Budując opakowanie, możemy to zrobić.
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, a inne szczegóły na temat różnych metod znajdują się w linku tutaj
Wiele sposobów, dzięki którym można to zrobić, a są one następujące,
Metoda 1: Korzystanie z opakowania.
Metoda 2: Używanie tego samego jako funkcji ogólnej w Array.prototype
Array.prototype.forEachAsync.js
Stosowanie :
Metoda 3:
Korzystanie z Promise.all
Metoda 4: Tradycyjna dla pętli lub nowoczesna dla pętli
źródło
Promise.all
powinny zostać użyte - nie uwzględniają żadnego z wielu przypadków skrajnych.Promise.all
.Promise.all
nie jest to możliwe, aleasync
/await
jest. I nie,forEach
absolutnie nie obsługuje żadnych obietnic błędów.To rozwiązanie jest również zoptymalizowane pod kątem pamięci, dzięki czemu można go uruchomić na 10 000 elementów danych i żądań. Niektóre inne rozwiązania tutaj spowodują awarię serwera na dużych zestawach danych.
W TypeScript:
Jak używać?
źródło
Możesz użyć
Array.prototype.forEach
, ale async / await nie jest tak kompatybilny. Jest tak, ponieważ obietnica zwrócona z wywołania zwrotnego asynchronicznego oczekuje, że zostanie rozwiązana, aleArray.prototype.forEach
nie rozwiązuje żadnych obietnic z wykonania jej wywołania zwrotnego. Więc możesz użyć forEach, ale będziesz musiał sam poradzić sobie z obietnicą.Oto sposób na odczyt i wydruk każdego pliku w szeregu przy użyciu
Array.prototype.forEach
Oto sposób (wciąż używany
Array.prototype.forEach
) do równoległego drukowania zawartości plikówźródło
Podobnie jak Antonio Val's
p-iteration
, alternatywny moduł npm toasync-af
:Alternatywnie
async-af
ma metodę statyczną (log / logAF), która rejestruje wyniki obietnic:Jednak główną zaletą biblioteki jest to, że można połączyć metody asynchroniczne, aby wykonać coś takiego:
async-af
źródło
Aby zobaczyć, jak to może pójść nie tak, wydrukuj console.log na końcu metody.
Rzeczy, które ogólnie mogą pójść nie tak:
Nie zawsze są one błędne, ale często występują w standardowych przypadkach użycia.
Ogólnie rzecz biorąc, użycie forEach da wszystko oprócz ostatniego. Wywoła każdą funkcję bez oczekiwania na funkcję, co oznacza, że wszystkie funkcje mają się uruchomić, a następnie zakończyć, nie czekając na zakończenie funkcji.
Jest to przykład w natywnym JS, który zachowa porządek, zapobiegnie przedwczesnemu powrotowi funkcji i teoretycznie zachowa optymalną wydajność.
Spowoduje to:
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 jako pierwsze.
Będzie również ładować wszystkie pliki jednocześnie, zamiast czekać na zakończenie pierwszego, zanim rozpocznie się odczyt drugiego pliku.
Jedyną wadą tej i oryginalnej wersji jest to, że jeśli wielokrotne odczyty są uruchamiane jednocześnie, trudniej jest obsługiwać błędy z powodu większej liczby błędów, które mogą wystąpić jednocześnie.
W wersjach, które odczytują plik na raz, zatrzyma się on na awarii bez marnowania czasu na próby odczytania kolejnych plików. Nawet przy skomplikowanym systemie anulowania może być trudne uniknięcie awarii pierwszego pliku, ale także odczytu większości innych plików.
Wydajność nie zawsze jest przewidywalna. Podczas gdy wiele systemów będzie działało szybciej z równoległymi odczytami plików, niektóre wolą sekwencję. Niektóre są dynamiczne i mogą się przesuwać pod obciążeniem, optymalizacje, które oferują opóźnienia, nie zawsze dają dobrą przepustowość przy silnej rywalizacji.
W tym przykładzie nie ma również obsługi błędów. Jeśli coś wymaga, aby albo wszystkie zostały pomyślnie pokazane, albo wcale, nie zrobi tego.
Dogłębnie zaleca się eksperymentowanie z plikiem console.log na każdym etapie i fałszywymi rozwiązaniami do odczytu plików (zamiast tego losowe opóźnienie). Chociaż wiele rozwiązań wydaje się robić to 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:
źródło
Korzystałbym ze sprawdzonych (miliony pobrań tygodniowo) modułów pify i asynchronicznych . Jeśli nie znasz modułu asynchronicznego, zdecydowanie zalecamy sprawdzenie jego dokumentacji . Widziałem, jak wielu deweloperów marnuje czas na odtwarzanie swoich metod lub, co gorsza, utrudnia utrzymanie kodu asynchronicznego, gdy metody asynchroniczne wyższego rzędu uprościłyby kod.
źródło