질문자 :Saad
forEach
루프에서 async
/ await
를 사용하는 데 문제가 있습니까? 파일 배열을 반복하고 각 파일의 내용을 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
실제로 사용할 수 없습니다. await
가 예상대로 작동하는 최신 for … of
루프를 대신 사용하세요.
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) })); }
BergiES2018을 사용하면 위의 모든 답변을 다음과 같이 크게 단순화할 수 있습니다.
async function printFiles () { const files = await getFilePaths() for await (const contents of files.map(file => fs.readFile(file, 'utf8'))) { console.log(contents) } }
사양 참조: proposal-async-iteration
2018-09-10: 이 답변은 최근 많은 관심을 받고 있습니다. 비동기식 반복에 대한 자세한 내용 은 Axel Rauschmayer의 블로그 게시물을 참조하십시오.
Francisco MateoPromise.all
Array.prototype.map
과 함께 사용하는 대신 Promise
가 해결되는 순서를 보장하지 않음), 해결된 Promise
Array.prototype.reduce
사용합니다.
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()); }
Timothy Zornnpm의 p-iteration 모듈은 Array 반복 방법을 구현하므로 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); }); })();
Antonio Val다음은 몇 가지 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)); }
당신이 당신의 자신의 코드에서이 포함될 수 있습니다 동안 참고, 당신이 당신이 (자신의 전역을 오염을 방지하기 위해) 다른 사람에게 배포 라이브러리에 포함하지 않아야합니다.
Matt@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); }
fs.readFile
은 어쨌든 Promise 객체를 반환하기 때문에 .map()
전달된 함수는 async
일 필요가 없습니다. 따라서 promises
Promise.all()
로 보낼 수 있는 Promise 객체의 배열입니다.
@Bergi의 답변에서 콘솔은 파일 내용을 읽은 순서대로 기록할 수 있습니다. 예를 들어 정말 작은 파일이 정말 큰 파일보다 먼저 읽기를 마치면 작은 파일이 files
배열의 큰 파일 뒤에 오더라도 먼저 기록됩니다. 그러나 위의 방법에서는 콘솔이 제공된 배열과 동일한 순서로 파일을 기록할 것임을 보장합니다.
chharvey1000단어 가치의 그림 - 순차 접근 전용
배경 : 어젯밤에 저도 비슷한 상황이었습니다. foreach 인수로 비동기 함수를 사용했습니다. 결과는 예측할 수 없었습니다. 내 코드를 3번 테스트했을 때 2번은 문제 없이 실행되었고 1번은 실패했습니다. (뭔가 이상해)
마침내 나는 머리를 굴리고 스크래치 패드 테스트를 했습니다.
시나리오 1 - foreach에서 비동기로 얻을 수 있는 비순차적
const getPromise = (time) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`Promise resolved for ${time}s`) }, time) }) } const main = async () => { const myPromiseArray = [getPromise(1000), getPromise(500), getPromise(3000)] console.log('Before For Each Loop') myPromiseArray.forEach(async (element, index) => { let result = await element; console.log(result); }) console.log('After For Each Loop') } main();
시나리오 2 - 위의 @Bergi가 제안한 대로 for - of
const getPromise = (time) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`Promise resolved for ${time}s`) }, time) }) } const main = async () => { const myPromiseArray = [getPromise(1000), getPromise(500), getPromise(3000)] console.log('Before For Each Loop') // AVOID USING THIS // myPromiseArray.forEach(async (element, index) => { // let result = await element; // console.log(result); // }) // This works well for (const element of myPromiseArray) { let result = await element; console.log(result) } console.log('After For Each Loop') } main();
저처럼 오래된 학교라면 클래식 for 루프를 사용하면 됩니다. 그것도 작동합니다. :)
const getPromise = (time) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`Promise resolved for ${time}s`) }, time) }) } const main = async () => { const myPromiseArray = [getPromise(1000), getPromise(500), getPromise(3000)] console.log('Before For Each Loop') // AVOID USING THIS // myPromiseArray.forEach(async (element, index) => { // let result = await element; // console.log(result); // }) // This works well too - the classic for loop :) for (let i = 0; i < myPromiseArray.length; i++) { const result = await myPromiseArray[i]; console.log(result); } console.log('After For Each Loop') } main();
누군가에게 도움이 되기를 바랍니다. 좋은 하루, 건배!
krupesh Anadkat또한 이 솔루션은 메모리에 최적화되어 있으므로 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 ... })
Oliver Dixon직렬화된 순서로 비동기 데이터를 처리하고 코드에 보다 일반적인 풍미를 제공하는 몇 가지 메서드를 파일에 표시하는 것은 매우 고통스럽습니다. 예를 들어:
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)); }
Jay EdwardsBergi의 솔루션 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
master_dodo위의 두 솔루션 모두 작동하지만 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)))
Hooman Askari한 가지 중요한 주의 사항 은 await + for .. of
method와 forEach + async
방식이 실제로 다른 효과를 낸다는 것입니다.
실제 for
루프 내에서 await
하면 모든 비동기 호출이 하나씩 실행됩니다. 그리고 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); } }
LeOn - Han Li작동하지 않는 forEach()
await 루프를 교체하기 위한 간단한 드롭인 솔루션 forEach
를 map
교체하고 시작 부분에 Promise.all(
을 추가하는 것입니다.
예를 들어:
await y.forEach(async (x) => {
에게
await Promise.all(y.map(async (x) => {
끝에 )
가 필요합니다.
yeah22원래 답변에 추가하기 만하면됩니다.
- 원래 답변의 병렬 읽기 구문은 때때로 혼란스럽고 읽기 어렵습니다. 다른 접근 방식으로 작성할 수 있습니다.
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); } }
gsaandy@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()
추신
readFilesQueue
console.log
에 의해 도입된 부작용*을 일으키는 printFiles
외부에 있으므로 모의, 테스트 및/또는 스파이하는 것이 더 좋으므로 내용(sidenote)을 반환하는 함수를 갖는 것은 쿨하지 않습니다.
따라서 코드는 다음과 같이 간단하게 설계할 수 있습니다. "순수"**하고 부작용이 없는 세 개의 분리된 기능, 전체 목록을 처리하고 실패한 경우를 처리하도록 쉽게 수정할 수 있습니다.
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)
향후 편집/현재 상태
Node는 최상위 대기를 지원합니다(이것은 아직 플러그인이 없으며, 조화 플래그를 통해 활성화할 수 있음). 멋지지만 한 가지 문제를 해결하지는 못합니다(전략적으로는 LTS 버전에서만 작동합니다). 파일을 얻는 방법?
구성을 사용합니다. 코드가 주어지면 이것이 모듈 내부에 있다는 느낌을 주므로 이를 수행하는 기능이 있어야 합니다. 그렇지 않은 경우 IIFE를 사용하여 역할 코드를 비동기 함수로 래핑하여 모든 작업을 수행하는 간단한 모듈을 생성해야 합니다. 그렇지 않으면 올바른 방법으로 구성할 수 있습니다.
// more complex version with IIFE to a single module (async (files) => readFiles(await files())(getFilesPath)
의미론으로 인해 변수 이름이 변경됩니다. 펑터(다른 함수에서 호출할 수 있는 함수)를 전달하고 응용 프로그램의 초기 논리 블록을 포함하는 메모리에 대한 포인터를 받습니다.
하지만 모듈이 아니고 로직을 내보내야 한다면?
비동기 함수에서 함수를 래핑합니다.
export const readFilesQueue = async () => { // ... to code goes here }
아니면 변수 이름을 변경하든지...
*
부작용이란 IO와 같이 상태/동작을 변경하거나 응용 프로그램에 버그를 유발할 수 있는 응용 프로그램의 부수적인 효과를 의미합니다.
**
"순수"는 콘솔 출력이 없고 데이터 조작만 있을 때 순수하지 않고 코드를 순수 버전으로 수렴할 수 있는 기능 때문에 아포스트로피입니다.
이 외에도, 순수하게 하려면 오류가 발생하기 쉬운 부작용을 처리하고 해당 오류를 애플리케이션과 별도로 처리하는 모나드로 작업해야 합니다.
lukaswilkeerTask, 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 */ }
인코딩에서 더 많은 유연성을 원한다면 이 작업을 수행할 수 있습니다(재미로 제안된 Pipe Forward 연산자를 사용하고 있습니다).
import { curry, flip } from 'ramda' export const readFile = fs.readFile |> future, |> curry, |> flip export const readFileUtf8 = readFile('utf-8')
추신 - 콘솔에서 이 코드를 시도하지 않았습니다. 약간의 오타가 있을 수 있습니다... "스트레이트 프리스타일, 돔 상단에서 벗어났습니다!" 90년대 아이들이 말했듯이. :-NS
Babakness현재 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>
이전에 생성된 모든 배열에는 이 기능을 사용할 수 없습니다.
Beau오늘 나는 이에 대한 여러 솔루션을 발견했습니다. forEach 루프에서 async await 함수를 실행합니다. 래퍼를 구축함으로써 이를 가능하게 할 수 있습니다.
기본 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 루프 또는 현대적인 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);
PranavKAndroArray.prototype.forEach
를 사용할 수 있지만 async/await는 그렇게 호환되지 않습니다. 이는 비동기 콜백에서 반환된 약속이 해결될 것으로 예상하지만 Array.prototype.forEach
가 콜백 실행에서 약속을 해결하지 않기 때문입니다. 따라서 forEach를 사용할 수 있지만 Promise 해결은 직접 처리해야 합니다.
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) }
richytong루프에서 비동기 메서드를 호출하는 것은 좋지 않습니다. 이는 전체 비동기 작업이 완료될 때까지 각 루프 반복이 지연되기 때문입니다. 성능이 좋지 않습니다. 또한 async/ await
의 병렬화 이점을 async
있습니다.
더 나은 솔루션은 한 번에 모든 약속을 만든 다음 Promise.all()
사용하여 결과에 액세스하는 것입니다. 그렇지 않으면 이전 작업이 완료될 때까지 각 연속 작업이 시작되지 않습니다.
결과적으로 코드는 다음과 같이 리팩토링될 수 있습니다.
const printFiles = async () => { const files = await getFilePaths(); const results = []; files.forEach((file) => { results.push(fs.readFile(file, 'utf8')); }); const contents = await Promise.all(results); console.log(contents); }
Johnz이것이 어떻게 잘못될 수 있는지 보려면 메소드 끝에 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}`); })();
jgmjgm다음은 forEach 루프에서 비동기를 사용하는 좋은 예입니다.
자신만의 asyncForEach 작성
async function asyncForEach(array, callback) { for (let index = 0; index < array.length; index++) { await callback(array[index], index, array) } }
이렇게 사용하시면 됩니다
await asyncForEach(array, async function(item,index,array){ //await here } )
ChenZeTongAntonio 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();
또는 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
Scott Rudiger다른 답변에서 언급했듯이 병렬이 아닌 순차적으로 실행되기를 원할 것입니다. 즉. 첫 번째 파일에 대해 실행하고 완료될 때까지 기다린 다음 완료되면 두 번째 파일에 대해 실행합니다. 그것은 일어나지 않을 것입니다.
왜 이런 일이 일어나지 않는지 해결하는 것이 중요하다고 생각합니다.
forEach
가 어떻게 작동하는지 생각해 보십시오. 소스를 찾을 수 없지만 다음과 같이 작동한다고 가정합니다.
const forEach = (arr, cb) => { for (let i = 0; i < arr.length; i++) { cb(arr[i]); } };
이제 다음과 같은 작업을 수행할 때 어떤 일이 발생하는지 생각해 보십시오.
forEach(files, async logFile(file) { const contents = await fs.readFile(file, 'utf8'); console.log(contents); });
forEach
의 for
루프 내에서 cb(arr[i])
호출하고 있으며 이는 결국 logFile(file)
입니다. logFile
함수 await
가 있으므로 for
i++
진행하기 전에 await
를 기다릴 것입니다.
아니, 그렇지 않습니다. 혼란스럽게도 그것은 await
작동하는 방식이 아닙니다. 문서에서 :
await는 실행 흐름을 분할하여 비동기 함수의 호출자가 실행을 재개할 수 있도록 합니다. await가 비동기 함수의 연속을 연기한 후 후속 명령문이 실행됩니다. 이 await가 함수 실행에 의해 실행된 마지막 표현식인 경우 await의 함수 완료에 대한 보류 중인 약속을 함수 호출자에게 반환하고 해당 호출자의 실행을 다시 시작하여 계속됩니다.
"b"
앞에 숫자가 기록되지 않습니다.
const delay = (ms) => { return new Promise((resolve) => { setTimeout(resolve, ms); }); }; const logNumbers = async () => { console.log(1); await delay(2000); console.log(2); await delay(2000); console.log(3); }; const main = () => { console.log("a"); logNumbers(); console.log("b"); }; main();
forEach
돌아가면 forEach
는 main
과 같고 logFile
logNumbers
와 같습니다. main
이유만으로 멈추지 않을 것 logNumbers
몇 가지 않습니다 await
보내고, 그리고 forEach
이유만으로 멈추지 않을 것입니다 logFile
일부 수행 await
보내고있다.
Adam Zerner모든 요소를 동시에 반복하려면 다음을 수행하십시오.
async function asyncForEach(arr, fn) { await Promise.all(arr.map(fn)); }
모든 요소를 동시에 반복하지 않으려면(예: 매핑 함수에 부작용이 있거나 모든 배열 요소에 대해 매퍼를 한 번에 실행하는 것은 리소스 비용이 너무 많이 드는 경우):
옵션 A: 약속
function asyncForEachStrict(arr, fn) { return new Promise((resolve) => { arr.reduce( (promise, cur, idx) => promise .then(() => fn(cur, idx, arr)), Promise.resolve(), ).then(() => resolve()); }); }
옵션 B: 비동기/대기
async function asyncForEachStrict(arr, fn) { for (let idx = 0; idx < arr.length; idx += 1) { const cur = arr[idx]; await fn(cur, idx, arr); } }
Wojciech Majexports.getTat = async function () { for (const tatfcp of resp[0]) { const getProductResponcekey = params.pincode + '-' + tatfcp.productid + '-' + result[tatfcp.productid].reqQty + '-' + tatfcp.groups[0].dispatchwarehouseid; const redisResp = await redis.getRedis(getProductResponcekey); if (redisResp) { products.push(redisResp[0]); console.log('redis', redisResp[0]); } else { const getProductResponceData = await getProductResponce(resp[1], resp[2], params.pincode, tatfcp, data[1], data[2], data[8], gstandvendordata[1], data[9]); products.push(getProductResponceData); redis.setRedis(getProductResponcekey, getProductResponceData, config.redis.expiryTime1Day); } } };
이것이 나의 해결책이다
suraj gholap잘 테스트된(주당 수백만 건의 다운로드) pify 및 async 모듈을 사용합니다. async 모듈에 익숙하지 않은 경우 해당 문서 를 확인하는 것이 좋습니다. 여러 개발자가 메서드를 다시 만드는 데 시간을 낭비하거나 더 나쁜 경우 고차 비동기 메서드가 코드를 단순화할 때 유지 관리하기 어려운 비동기 코드를 만드는 것을 보았습니다.
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 ```
Zachary Ryan Smith출처 : http:www.stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop