[javascript] forEach 루프와 함께 async / await 사용

루프 에서 async/ 사용에 문제가 있습니까? 파일 배열과 각 파일의 내용 을 반복하려고 합니다.awaitforEachawait

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.mapPromiseArray.prototype.reducePromise

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 객체의 배열은로 보낼 수 있습니다 .asyncfs.readFilepromisesPromise.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