forEach 루프와 함께 async / await 사용


1128

루프 에서 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난 그냥 어떤 문제는이와 함께이 있다면 물어보고 싶은게, 그래서 이런 고차 함수.

답변:


2144

코드가 작동하는지는 확실하지만 예상대로 작동하지 않습니다. 여러 비동기 호출을 발생 시키지만 그 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)
  }));
}

33
for ... of ...작동 하는지 설명해 주 시겠습니까?
Demonbane

84
알았어 이유를 알고 ... Babel을 사용하면 생성기 기능을 변환 async/ 변환 await하고 사용 forEach하면 각 반복에 개별 생성기 기능이 있으며 다른 생성기와는 아무런 관련이 없습니다. 그래서 그들은 독립적으로 처형 될 것이며 next()다른 사람들과의 맥락이 없습니다 . 실제로 for()반복은 하나의 단일 생성기 함수에 있기 때문에 간단한 루프도 작동합니다.
Demonbane

21
@Demonbane : 간단히 말해서 작동하도록 설계 되었기 때문에 :-) await는 모든 제어 구조를 포함 하여 현재 기능 평가를 일시 중단합니다 . 예, 그것은 발전기와 매우 유사합니다 (따라서 비동기 / 대기를 polyfill하는 데 사용됩니다).
Bergi

3
@ arve0 실제로는 async함수가 Promise실행기 콜백과 크게 다르지 않지만, map두 경우 모두 콜백이 약속을 반환합니다.
Bergi

5
JS 약속에 대해 배울 때는 30 분 라틴어 번역을 사용하십시오. 당신에게있는 거 자랑 @Bergi 희망)
펠릭스 Gagnon의-Grenier의를

188

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 : 비동기 반복


4
비동기 반복에 대해 더 알고 싶은 사람을 위해 답변에 사양 에 대한 링크를 넣을 수 있다면 좋을 것 입니다.
saadq

8
이 반복자의 내용 대신 파일 안
FluffyBeing

10
사람들이 왜이 답변을지지합니까? 답변, 질문 및 제안을 자세히 살펴보십시오. 뒤에는 of배열을 반환하는 async 함수가 있어야합니다. 작동하지 않으며 프란시스코는 말했다.
Yevhenii Herasymchuk

3
@AntonioVal에 전적으로 동의합니다. 답이 아닙니다.
Yevhenii Herasymchuk

2
답변이 아니라는 데 동의하지만 제안서를지지하는 것은 그 인기를 높이고 나중에 사용할 수있게 만드는 방법입니다.
Robert Molina

61

( 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());
}

1
이것은 완벽하게 작동합니다. 대단히 감사합니다. 당신은 여기 무슨 일이 일어나고 있는지 설명 할 수 Promise.resolve()await promise;?
parrker9

1
이것은 꽤 멋지다. 파일을 한 번에 읽지 않고 순서대로 읽을 것이라고 생각하는 것이 맞습니까?
GollyJer

1
@ parrker9는 Promise.resolve()이미 해결 반환 Promise즉, 그래서 객체를 reducePromise시작하는 방법을. 체인 await promise;의 마지막 Promise이 해결 될 때까지 기다립니다 . @GollyJer 파일은 한 번에 하나씩 순차적으로 처리됩니다.
Timothy Zorn

의견에 감사드립니다. 주석에 언급 된 다른 방법 중 일부와 달리이 방법은 동기 적이므로 파일이 순차적으로 읽히지 않고 병렬로 읽히지 않는다는 것을 의미합니다 (다음 축소 반복은 이전에 의존하기 때문에 반복, 그것은 동 기적이어야한다).
Shay Yzhakov

1
@ Shay, 당신은 동기식이 아닌 순차적 인 것을 의미합니다. 이것은 여전히 ​​비동기 적입니다. 다른 것들이 스케줄되면, 여기에서 반복 사이에서 실행될 것입니다.
Timothy Zorn

32

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);
  });
})();

1
JS 자체와 동일한 기능 / 메소드를 가지고 있기 때문에 나는 이것을 좋아한다. 내 경우에는 some오히려 필요하다 forEach. 감사!
mikemaccana

25

다음은 몇 가지 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));
}

참고 자신의 코드에서이 문제를 포함 할 수 있지만, 당신이 당신이 (자신의 전역을 오염을 방지하기 위해) 다른 사람에게 배포 라이브러리에 포함하지 않아야합니다.


1
프로토 타입에 직접 추가하는 것을 주저하고 있지만, 이것은 각 구현에 대한 훌륭한 비동기입니다.
DaniOcean

2
미래에 이름이 독특하다면 (사용할 것처럼 _forEachAsync) 이것은 합리적입니다. 또한 많은 상용구 코드를 저장하므로 가장 좋은 대답이라고 생각합니다.
mikemaccana

1
@estus 그것은 다른 사람들의 코드를 오염시키지 않기위한 것입니다. 코드가 개인 조직에 속해 있고 전역이 잘 식별 된 파일에 있으면 ( globals.js좋을 것입니다) 원하는대로 전역을 추가 할 수 있습니다.
mikemaccana

1
@mikemaccana 일반적으로 받아 들여지는 나쁜 습관을 피하는 것입니다. 사실, 이는 거의 발생하지 않는 자사 코드 만 사용하는 한 수행 할 수 있습니다. 문제는 타사 라이브러리를 사용할 때 같은 방식으로 느끼고 동일한 전역을 수정하는 다른 사람이있을 수 있다는 것입니다 .lib가 작성된 시점에 좋은 생각처럼 보였기 때문입니다.
Estus Flask

1
@estus 물론입니다. 나는 여기에 (특히 생산적이지 않은) 토론을 저장하기 위해 질문에 경고를 추가했습니다.
mikemaccana

6

@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됩니다. 그러나 위의 방법에서 콘솔은 제공된 배열과 동일한 순서로 파일을 기록합니다.


1
나는 당신이 틀렸다고 확신합니다 : 당신의 방법이 파일을 순서대로 읽을 수 있다고 확신합니다. 예. (로 인해) 올바른 순서로 출력을 기록 await Promise.all하지만 파일이 다른 순서로 읽혀졌을 수 있습니다. 읽다".
Venryx

1
@Venryx 수정 해 주셔서 감사합니다. 내 답변을 업데이트했습니다.
chharvey

5

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

4

그러나 위의 두 가지 솔루션 모두 작동하지만 Antonio 's는 코드를 적게 사용하여 작업을 수행합니다. 여기에서 여러 다른 자식 참조에서 데이터베이스의 데이터를 확인한 다음 모든 데이터를 배열로 푸시하고 약속대로 해결하는 방법이 있습니다. 끝난:

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)))

3

비 동기화 된 데이터를 직렬화 된 순서로 처리하고 코드에보다 전통적인 풍미를주는 파일에 몇 가지 메소드를 팝하는 것은 꽤 고통스럽지 않습니다. 예를 들면 다음과 같습니다.

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;
  };
};

이제 그것이 './myAsync.js'에 저장되었다고 가정하면 인접한 파일에서 아래와 비슷한 것을 할 수 있습니다.

...
/* 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));
}

2
사소한 부록, try / catch 블록에서 당신의 await / asyncs를 감싸는 것을 잊지 마십시오 !!
Jay Edwards

3

@Bergi의 응답과 비슷하지만 한 가지 차이점이 있습니다.

Promise.all 거부 될 경우 모든 약속을 거부합니다.

따라서 재귀를 사용하십시오.

const readFilesQueue = async (files, index = 0) {
    const contents = await fs.readFile(files[index], 'utf8')
    console.log(contents)

    return files.length <= index
        ? readFilesQueue(files, ++index)
        : files

}

const printFiles async = () => {
    const files = await getFilePaths();
    const printContents = await readFilesQueue(files)

    return printContents
}

printFiles()

추신

readFilesQueueprintFiles의해 도입 된 부작용 * 의 원인 이 아닌 경우, console.log모의, 테스트 및 스파이하는 것이 좋습니다. 따라서 콘텐츠 (측면 참고)를 반환하는 함수를 사용하는 것은 좋지 않습니다.

따라서 코드는 다음과 같이 간단하게 설계 할 수 있습니다. "순수한"**이고 부작용이없는 세 개의 분리 된 함수는 전체 목록을 처리하며 실패한 사례를 처리하기 위해 쉽게 수정할 수 있습니다.

const files = await getFilesPath()

const printFile = async (file) => {
    const content = await fs.readFile(file, 'utf8')
    console.log(content)
}

const readFiles = async = (files, index = 0) => {
    await printFile(files[index])

    return files.lengh <= index
        ? readFiles(files, ++index)
        : files
}

readFiles(files)

향후 편집 / 현재 상태

노드는 최상위 레벨 대기를 지원합니다 (이것은 아직 플러그인이 없으며 조화 플래그를 통해 사용할 수 없으며 활성화 될 수 있음). 멋지지만 한 가지 문제는 해결하지 못합니다 (전략적으로는 LTS 버전에서만 작동합니다). 파일을 얻는 방법?

구성 사용. 코드가 주어지면 이것이 모듈 내부에 있다는 느낌을 주므로 그렇게하는 기능이 있어야합니다. 그렇지 않은 경우 IIFE를 사용하여 역할 코드를 비동기 함수로 래핑하여 간단한 모듈을 작성하여 모든 작업을 수행하거나 올바른 방식으로 구성 할 수 있습니다.

// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)

의미론으로 인해 변수 이름이 변경됩니다. functor (다른 함수에서 호출 할 수있는 함수)를 전달하고 응용 프로그램의 초기 논리 블록이 포함 된 메모리의 포인터를받습니다.

그러나 모듈이 아닌 경우 로직을 내 보내야합니까?

함수를 비동기 함수로 래핑하십시오.

export const readFilesQueue = async () => {
    // ... to code goes here
}

또는 변수 이름을 변경하십시오 ...


* 부작용으로 IO와 같이 응용 프로그램의 statate / behaviour 또는 introuce 버그를 변경할 수있는 응용 프로그램의 공동 효과를 의미합니다.

** "순수하게", 그것은 순수하지 않은 함수이고 콘솔 출력이없고 데이터 조작 만있을 때 코드가 순수한 버전으로 수렴 될 수 있기 때문에 아포스트로피입니다.

이 외에도 순수하게 부작용을 처리하고 오류가 발생하기 쉬운 모나드를 사용하고 해당 오류를 애플리케이션과 별도로 처리해야합니다.


2

한 가지 중요한 경고 는 다음 await + for .. of과 같습니다. 방법과 forEach + async방법이 실제로 다른 효과를 갖습니다.

await실제 for루프 안에 있으면 모든 비동기 호출이 하나씩 실행됩니다. 그리고 그 forEach + async방법은 동시에 모든 약속을 해고 할 것입니다. 더 빠르지 만 때로는 압도적입니다 ( DB 쿼리를 수행하거나 볼륨 제한이있는 일부 웹 서비스를 방문 하고 한 번에 100,000 건의 호출을 시작하지 않으려는 경우).

사용 reduce + promise하지 않고 async/await파일을 하나씩 읽도록 하려면 (아주 우아함) 사용할 수도 있습니다 .

files.reduce((lastPromise, file) => 
 lastPromise.then(() => 
   fs.readFile(file, 'utf8')
 ), Promise.resolve()
)

또는 forEachAsync를 작성하여 기본적으로 동일한 for 루프 기반을 사용할 수 있습니다.

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}

for in에 나타나지 않도록 Array.prototype 및 Object.prototype에서 javascript로 메소드를 정의하는 방법을 살펴보십시오 . 또한 forEach반복성에 의존하는 대신 인덱스에 액세스 하는 네이티브와 동일한 반복을 사용 하고 인덱스를 콜백에 전달해야합니다.
Bergi

Array.prototype.reduce비동기 기능을 사용하는 방식으로 사용할 수 있습니다 . 내 대답에 예를 표시했습니다. stackoverflow.com/a/49499491/2537258
Timothy Zorn

2

Task, futurize 및 traversable List를 사용하면 간단하게 할 수 있습니다.

async function printFiles() {
  const files = await getFiles();

  List(files).traverse( Task.of, f => readFile( f, 'utf-8'))
    .fork( console.error, console.log)
}

설정 방법은 다음과 같습니다.

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)

원하는 코드를 구성하는 또 다른 방법은

const printFiles = files => 
  List(files).traverse( Task.of, fn => readFile( fn, 'utf-8'))
    .fork( console.error, console.log)

또는 더 기능 지향적 인

// 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)

그런 다음 부모 함수에서

async function main() {
  /* awesome code with side-effects before */
  printFiles( await getFiles() );
  /* awesome code with side-effects after */
}

인코딩에서 더 많은 유연성을 원한다면이 작업을 수행 할 수 있습니다 (재미있는 경우 파이프 제안 연산자를 사용하고 있습니다 )

import { curry, flip } from 'ramda'

export const readFile = fs.readFile 
  |> future,
  |> curry,
  |> flip

export const readFileUtf8 = readFile('utf-8')

추신-콘솔 에서이 코드를 시도하지 않았으며 오타가있을 수 있습니다 ... "돔의 꼭대기에서 똑 바른 자유형!" 90 년대 아이들이 말하듯이 :-피


2

현재 Array.forEach 프로토 타입 속성은 비동기 작업을 지원하지 않지만 필요에 맞게 자체 폴리 필을 만들 수 있습니다.

// Example of asyncForEach Array poly-fill for NodeJs
// file: asyncForEach.js
// Define asynForEach function 
async function asyncForEach(iteratorFunction){
  let indexer = 0
  for(let data of this){
    await iteratorFunction(data, indexer)
    indexer++
  }
}
// Append it as an Array prototype property
Array.prototype.asyncForEach = asyncForEach
module.exports = {Array}

그리고 그게 다야! 이제 이들에 대해 조작에 대해 정의 된 배열에서 비동기 forEach 메소드를 사용할 수 있습니다.

테스트 해보자 ...

// Nodejs style
// file: someOtherFile.js

const readline = require('readline')
Array = require('./asyncForEach').Array
const log = console.log

// Create a stream interface
function createReader(options={prompt: '>'}){
  return readline.createInterface({
    input: process.stdin
    ,output: process.stdout
    ,prompt: options.prompt !== undefined ? options.prompt : '>'
  })
}
// Create a cli stream reader
async function getUserIn(question, options={prompt:'>'}){
  log(question)
  let reader = createReader(options)
  return new Promise((res)=>{
    reader.on('line', (answer)=>{
      process.stdout.cursorTo(0, 0)
      process.stdout.clearScreenDown()
      reader.close()
      res(answer)
    })
  })
}

let questions = [
  `What's your name`
  ,`What's your favorite programming language`
  ,`What's your favorite async function`
]
let responses = {}

async function getResponses(){
// Notice we have to prepend await before calling the async Array function
// in order for it to function as expected
  await questions.asyncForEach(async function(question, index){
    let answer = await getUserIn(question)
    responses[question] = answer
  })
}

async function main(){
  await getResponses()
  log(responses)
}
main()
// Should prompt user for an answer to each question and then 
// log each question and answer as an object to the terminal

map과 같은 다른 배열 함수 중 일부에 대해서도 동일한 작업을 수행 할 수 있습니다 ...

async function asyncMap(iteratorFunction){
  let newMap = []
  let indexer = 0
  for(let data of this){
    newMap[indexer] = await iteratorFunction(data, indexer, this)
    indexer++
  }
  return newMap
}

Array.prototype.asyncMap = asyncMap

... 등등 :)

참고할 사항 :

  • iteratorFunction은 비동기 함수 또는 약속이어야합니다
  • 이전에 생성 된 어레이 Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>는이 기능을 사용할 수 없습니다

2

원래 답변에 추가하기 만하면됩니다.

  • 원래 답변의 병렬 읽기 구문은 때로는 혼란스럽고 읽기 어렵습니다. 어쩌면 다른 접근법으로 작성할 수 있습니다.
async function printFiles() {
  const files = await getFilePaths();
  const fileReadPromises = [];

  const readAndLogFile = async filePath => {
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
    return contents;
  };

  files.forEach(file => {
    fileReadPromises.push(readAndLogFile(file));
  });

  await Promise.all(fileReadPromises);
}
  • for ... of 뿐만 아니라 순차 작업 경우 정상적인 for 루프도 작동합니다.
async function printFiles() {
  const files = await getFilePaths();

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
  }
}

1

오늘 나는 이것을 위해 여러 가지 솔루션을 발견했습니다. forEach 루프에서 비동기 대기 기능을 실행하십시오. 래퍼를 만들어서 이런 일을 할 수 있습니다.

내부 forEach에 대해 내부적으로 작동하는 방법과 비동기 함수 호출을 수행 할 수없는 이유 및 다양한 메소드에 대한 기타 세부 정보가 여기 링크에 제공됩니다.

이를 수행 할 수있는 여러 가지 방법은 다음과 같습니다.

방법 1 : 랩퍼 사용.

await (()=>{
     return new Promise((resolve,reject)=>{
       items.forEach(async (item,index)=>{
           try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
           count++;
           if(index === items.length-1){
             resolve('Done')
           }
         });
     });
    })();

방법 2 : Array.prototype의 일반 함수와 동일하게 사용

Array.prototype.forEachAsync.js

if(!Array.prototype.forEachAsync) {
    Array.prototype.forEachAsync = function (fn){
      return new Promise((resolve,reject)=>{
        this.forEach(async(item,index,array)=>{
            await fn(item,index,array);
            if(index === array.length-1){
                resolve('done');
            }
        })
      });
    };
  }

사용법 :

require('./Array.prototype.forEachAsync');

let count = 0;

let hello = async (items) => {

// Method 1 - Using the Array.prototype.forEach 

    await items.forEachAsync(async () => {
         try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
        count++;
    });

    console.log("count = " + count);
}

someAPICall = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("done") // or reject('error')
        }, 100);
    })
}

hello(['', '', '', '']); // hello([]) empty array is also be handled by default

방법 3 :

Promise.all 사용

  await Promise.all(items.map(async (item) => {
        await someAPICall();
        count++;
    }));

    console.log("count = " + count);

방법 4 : 전통적인 for 루프 또는 modern for 루프

// Method 4 - using for loop directly

// 1. Using the modern for(.. in..) loop
   for(item in items){

        await someAPICall();
        count++;
    }

//2. Using the traditional for loop 

    for(let i=0;i<items.length;i++){

        await someAPICall();
        count++;
    }


    console.log("count = " + count);

방법 1과 2는 Promise.all사용되어야 할 위치 가 잘못 구현 된 것입니다. 많은 경우를 고려하지 않습니다.
Bergi

@ Bergi : 유효한 의견에 감사드립니다. 방법 1과 2가 왜 틀린지 설명 해주십시오. 또한 목적을 수행합니다. 이것은 매우 잘 작동합니다. 이것은 하나를 선택하기로 결정할 수있는 상황에 따라 이러한 모든 방법이 가능하다는 것입니다. 나는 같은 예제를 가지고있다.
PranavKAndro

빈 배열에서 실패하며 오류 처리가 없으며 아마도 더 많은 문제가 있습니다. 바퀴를 재발 명하지 마십시오. 그냥 사용하십시오 Promise.all.
Bergi

불가능한 특정 상황에서는 도움이 될 것입니다. 또한 오류 처리는 기본적으로 forEach API에 의해 수행되므로 문제가 없습니다. 돌봐!
PranavKAndro

아니요, Promise.all불가능하지만 async/ 인 조건 은 없습니다 await. 그리고 아닙니다 forEach. 약속 오류는 절대 처리하지 않습니다.
Bergi

1

이 솔루션은 메모리에 최적화되어 있으므로 10,000 개의 데이터 항목 및 요청에서 실행할 수 있습니다. 여기에있는 다른 솔루션 중 일부는 큰 데이터 세트에서 서버를 중단시킵니다.

TypeScript에서 :

export async function asyncForEach<T>(array: Array<T>, callback: (item: T, index: number) => void) {
        for (let index = 0; index < array.length; index++) {
            await callback(array[index], index);
        }
    }

사용하는 방법?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})

1

을 사용할 수 Array.prototype.forEach있지만 async / await는 호환되지 않습니다. 비동기 콜백에서 반환 된 약속은 해결 될 것으로 예상되지만 Array.prototype.forEach콜백 실행으로 인한 약속은 해결되지 않기 때문입니다. 따라서 forEach를 사용할 수 있지만 약속 해결을 직접 처리해야합니다.

다음은 각 파일을 연속적으로 읽고 인쇄하는 방법입니다. Array.prototype.forEach

async function printFilesInSeries () {
  const files = await getFilePaths()

  let promiseChain = Promise.resolve()
  files.forEach((file) => {
    promiseChain = promiseChain.then(() => {
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    })
  })
  await promiseChain
}

다음은 Array.prototype.forEach파일의 내용을 병렬로 인쇄 하는 방법 (여전히 사용 )입니다

async function printFilesInParallel () {
  const files = await getFilePaths()

  const promises = []
  files.forEach((file) => {
    promises.push(
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    )
  })
  await Promise.all(promises)
}

첫 번째 시나리오는 세리로 실행해야하는 루프에 이상적이며 다음과 같은 용도로는 사용할 수 없습니다.
Mark Odey

0

Antonio Val 's와 유사하게 p-iteration대체 npm 모듈은 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();

또는 async-af약속 결과를 기록하는 정적 메소드 (log / logAF)가 있습니다.

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();

그러나 라이브러리의 주요 장점은 비동기 메소드를 연결하여 다음과 같은 작업을 수행 할 수 있다는 것입니다.

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();

async-af


0

이것이 어떻게 잘못 될 수 있는지 보려면, 메소드의 끝에 console.log를 인쇄하십시오.

일반적으로 잘못 될 수있는 것 :

  • 임의의 순서.
  • printFiles는 파일을 인쇄하기 전에 실행을 마칠 수 있습니다.
  • 성능이 저하됩니다.

항상 잘못된 것은 아니지만 표준 사용 사례에 자주 있습니다.

일반적으로 forEach를 사용하면 마지막을 제외한 모든 결과가 발생합니다. 함수를 기다리지 않고 각 함수를 호출합니다. 즉, 모든 함수가 시작되고 완료 될 때까지 기다리지 않고 완료되도록 지시합니다.

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()

이것은 순서를 유지하고 함수가 조기에 리턴되는 것을 방지하고 이론적으로 최적의 성능을 유지하는 기본 JS의 예입니다.

이것은 :

  • 병렬로 발생하는 모든 파일 읽기를 시작하십시오.
  • 기다릴 약속으로 파일 이름을 맵핑하기 위해 map을 사용하여 순서를 유지하십시오.
  • 배열에서 정의한 순서대로 각 약속을 기다립니다.

이 솔루션을 사용하면 다른 파일이 먼저 사용 가능할 때까지 기다리지 않고 사용 가능한 즉시 첫 번째 파일이 표시됩니다.

또한 두 번째 파일 읽기를 시작하기 전에 첫 번째 파일이 완료 될 때까지 기다리지 않고 모든 파일을 동시에로드합니다.

이것과 원래 버전의 유일한 단점은 한 번에 여러 번 읽기를 시작하면 한 번에 발생할 수있는 더 많은 오류가 있기 때문에 오류를 처리하기가 더 어렵다는 것입니다.

한 번에 파일을 읽는 버전을 사용하면 더 이상 파일을 읽는 데 시간을 낭비하지 않고 오류가 발생하면 중지됩니다. 정교한 취소 시스템을 사용하더라도 첫 번째 파일에서 실패하지만 다른 파일도 대부분 읽는 것을 피하기 어려울 수 있습니다.

성능을 항상 예측할 수있는 것은 아닙니다. 병렬 파일 읽기로 많은 시스템이 더 빠르지 만 일부는 순차적 인 것을 선호합니다. 일부는 동적이며로드 상태에서 전환 될 수 있습니다. 대기 시간을 제공하는 최적화로 인해 경쟁이 심할 때 처리량이 항상 좋은 것은 아닙니다.

이 예에서는 오류 처리도 없습니다. 무언가가 모두 성공적으로 표시되거나 전혀 표시되지 않아야하는 경우 그렇게하지 않습니다.

각 단계에서 console.log 및 가짜 파일 읽기 솔루션 (임의의 지연) 대신 심층적 인 실험이 권장됩니다. 많은 경우에 간단한 해결책으로도 많은 솔루션이 동일한 것으로 보이지만 모두 약간의 정밀한 조사가 필요한 미묘한 차이가 있습니다.

이 모의를 사용하여 솔루션 간의 차이점을 알 수 있습니다.

(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}`);
})();

-3

나는 잘 테스트 된 (주당 수백만 번의 다운로드) pifyasync 모듈을 사용합니다. 비동기 모듈에 익숙하지 않은 경우 해당 문서 를 확인하는 것이 좋습니다 . 여러 개발자가 메소드를 다시 작성하거나 시간을 낭비하여 더 높은 순서의 비동기 메소드가 코드를 단순화 할 때 유지 관리하기 어려운 비동기 코드를 만드는 데 시간을 낭비하는 것을 보았습니다.

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
```


이것은 잘못된 방향으로의 단계입니다. 다음은 현대적인 JS 시대에 콜백 지옥에 빠져 사람들을 돕기 위해 만든 매핑 가이드입니다 : github.com/jmjpro/async-package-to-async-await/blob/master/… .
jbustamovej

당신이로 여기에서 볼 수있는 , 내가 관심과 비동기를 사용하여 개방하고 / 대신 비동기 LIB의 기다리고 있습니다. 지금은 시간과 장소가 있다고 생각합니다. async lib == "callback hell"및 async / await == "현대 JS 시대"라고 확신하지 않습니다. imo, async lib> async / await 인 경우 : 1. 복잡한 흐름 (예 : 대기열,화물, 상황이 복잡 할 때도 자동) 2. 동시성 3. 배열 / 객체 / iterable 지원 4. 오류 처리
Zachary Ryan Smith
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.