[javascript] forEach 루프와 함께 async / await 사용
루프 에서 async
/ 사용에 문제가 있습니까? 파일 배열과 각 파일의 내용 을 반복하려고 합니다.await
forEach
await
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()
이 코드는 작동하지만 이것으로 뭔가 잘못 될 수 있습니까? 나는 누군가가 당신이 사용 안하고 말해 있었다 async
/ await
난 그냥 어떤 문제는이와 함께이 있다면 물어보고 싶은게, 그래서 이런 고차 함수.
답변
코드가 작동하는지는 확실하지만 예상대로 작동하지 않습니다. 여러 비동기 호출을 발생 시키지만 그 printFiles
후에 함수가 즉시 반환됩니다.
순서대로 읽기
파일을 순서대로 읽으려면 실제로 사용할 수 없습니다forEach
. for … of
대신 현대 루프를 사용하면 await
예상대로 작동합니다.
async function printFiles () {
const files = await getFilePaths();
for (const file of files) {
const contents = await fs.readFile(file, 'utf8');
console.log(contents);
}
}
병렬로 읽기
파일을 병렬로 읽으려면 실제로 사용할 수 없습니다forEach
. 각 async
콜백 함수 호출은 약속을 반환하지만 기다리지 않고 버립니다. map
대신 사용 하면 얻을 수있는 약속 배열을 기다릴 수 있습니다 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)
}));
}
답변
ES2018을 사용하면 다음에 대한 위의 모든 답변을 크게 단순화 할 수 있습니다.
async function printFiles () {
const files = await getFilePaths()
for await (const file of fs.readFile(file, 'utf8')) {
console.log(contents)
}
}
spec : proposal-async-iteration 참조
2018-09-10 :이 답변은 최근 많은 주목을 받고 있습니다 . 비동기 반복 에 대한 자세한 내용은 Axel Rauschmayer의 블로그 게시물을 참조하십시오 : ES2018 : 비동기 반복
답변
( s가 해결 되는 순서를 보장하지는 않음) Promise.all
과 함께 대신 resolved로 시작합니다 .Array.prototype.map
Promise
Array.prototype.reduce
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());
}
답변
npm 의 p-iteration 모듈은 Array iteration 메서드를 구현하여 async / await와 함께 매우 간단하게 사용할 수 있습니다.
귀하의 경우에 대한 예 :
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);
});
})();
답변
다음은 몇 가지 forEachAsync
프로토 타입입니다. 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));
}
참고 자신의 코드에서이 문제를 포함 할 수 있지만, 당신이 당신이 (자신의 전역을 오염을 방지하기 위해) 다른 사람에게 배포 라이브러리에 포함하지 않아야합니다.
답변
@Bergi의 답변 외에도 세 번째 대안을 제시하고 싶습니다. @Bergi의 두 번째 예제와 매우 유사하지만 각각 readFile
개별적 으로 기다리는 대신 약속을 배열합니다.
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);
}
어쨌든 Promise 객체를 반환 하므로 전달 된 함수는 일 .map()
필요는 없습니다 . 따라서 Promise 객체의 배열은로 보낼 수 있습니다 .async
fs.readFile
promises
Promise.all()
@ Bergi의 대답에서 콘솔은 파일 내용을 읽은 순서대로 기록 할 수 있습니다. 예를 들어, 아주 작은 파일이 아주 큰 파일보다 읽기를 마치면 작은 파일이 배열 의 큰 파일 뒤에 오는 경우에도 먼저 기록 files
됩니다. 그러나 위의 방법에서 콘솔은 제공된 배열과 동일한 순서로 파일을 기록합니다.
답변
Bergi의 솔루션fs
은 약속 기반 일 때 잘 작동합니다 . 당신은 사용할 수 있습니다 bluebird
, fs-extra
또는 fs-promise
이것에 대한.
그러나 노드의 기본 fs
라이브러리에 대한 솔루션 은 다음과 같습니다.
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;
}));
참고 :
require('fs')
강제로 세 번째 인수로 기능을 수행하고 그렇지 않으면 오류가 발생합니다.
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function