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
콜백 함수 호출은 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
( Promise
s가 해결 되는 순서를 보장하지 않음) 대신 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 객체의 배열입니다 .async
fs.readFile
promises
Promise.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();
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
'Programing' 카테고리의 다른 글
시간 초과로 Redux 작업을 전달하는 방법은 무엇입니까? (0) | 2020.09.29 |
---|---|
키로 사전을 정렬하려면 어떻게해야합니까? (0) | 2020.09.29 |
CommonJS, AMD 및 RequireJS 간의 관계? (0) | 2020.09.29 |
푸시되지 않은 git 커밋을 어떻게 삭제합니까? (0) | 2020.09.29 |
Python에서 싱글 톤 만들기 (0) | 2020.09.29 |