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:
- 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.
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.
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()
Jest to przykład w natywnym JS, który zachowa porządek, zapobiegnie przedwczesnemu powrotowi funkcji i teoretycznie zachowa optymalną wydajność.
Spowoduje to:
- Zainicjuj wszystkie odczyty pliku, aby miały miejsce równolegle.
- Zachowaj porządek za pomocą map do mapowania nazw plików na obietnice oczekiwania.
- Poczekaj na każdą obietnicę w kolejności określonej 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 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:
(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}`);
})();
for ... of ...
działa?