Programing

forEach 루프와 함께 async / await 사용

crosscheck 2020. 9. 29. 07:19
반응형

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콜백 함수 호출은 promise를 반환하지만 기다리는 대신 버립니다. 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)
  }
}

사양 참조 : 제안 비동기 반복


2018-09-10 :이 답변은 최근 많은 관심을 받고 있습니다 . 비동기 반복 에 대한 자세한 내용은 Axel Rauschmayer의 블로그 게시물을 참조하세요. ES2018 : 비동기 반복


Promise.all와 함께 Array.prototype.map( Promises가 해결 되는 순서를 보장하지 않음) 대신 resolved로 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 모듈은 async / await를 사용하여 매우 간단한 방식으로 사용할 수 있도록 Array 반복 메서드를 구현합니다.

케이스의 예 :

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됩니다. 그러나 위의 방법에서 콘솔은 읽는 것과 동일한 순서로 파일을 기록합니다.


위의 두 솔루션 모두 작동하지만 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)))

직렬화 된 순서로 비동기 데이터를 처리하고 코드에보다 일반적인 풍미를 제공하는 파일에 몇 가지 메서드를 팝하는 것은 매우 어렵지 않습니다. 예를 들면 :

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

한 가지 중요한 주의 사항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);
    }
}

Task, futurize 및 순회 가능한 목록을 사용하여 간단하게 수행 할 수 있습니다.

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 */
}

인코딩에 더 많은 유연성을 원하신다면이 작업을 수행 할 수 있습니다 (재미있게 저는 제안 된 Pipe Forward 연산자를 사용하고 있습니다 ).

import { curry, flip } from 'ramda'

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

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

추신-나는 콘솔에서이 코드를 시도하지 않았고, 약간의 오타가 있을지도 모른다 ... "직선 자유형, 돔 꼭대기에서!" 90 년대 아이들이 말했듯이. :-피


현재 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은 비동기 함수 또는 promise 여야합니다.
  • 이전에 생성 된 어레이 Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>는이 기능을 사용할 수 없습니다.

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

Antonio Val과 유사하게 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();

Alternatively, async-af has a static method (log/logAF) that logs the results of promises:

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

However, the main advantage of the library is that you can chain asynchronous methods to do something like:

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


I would use the well-tested (millions of downloads per week) pify and async modules. If you are unfamiliar with the async module, I highly recommend you check out its docs. I've seen multiple devs waste time recreating its methods, or worse, making difficult-to-maintain async code when higher-order async methods would simplify code.

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

참고URL : https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop

반응형