Javascript 在 forEach 循环中使用 async/await
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/37576685/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using async/await with a forEach loop
提问by saadq
Are there any issues with using async
/await
in a forEach
loop? I'm trying to loop through an array of files and await
on the contents of each file.
在循环中使用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()
This code does work, but could something go wrong with this? I had someone tell me that you're not supposed to use async
/await
in a higher order function like this, so I just wanted to ask if there was any issue with this.
这段代码确实有效,但会不会有什么问题?有人告诉我你不应该在这样的高阶函数中使用async
/ await
,所以我只是想问问这是否有任何问题。
回答by Bergi
Sure the code does work, but I'm pretty sure it doesn't do what you expect it to do. It just fires off multiple asynchronous calls, but the printFiles
function does immediately return after that.
当然代码确实有效,但我很确定它不会做你期望它做的事情。它只是触发多个异步调用,但printFiles
此后该函数会立即返回。
Reading in sequence
按顺序阅读
If you want to read the files in sequence, you cannot use forEach
indeed. Just use a modern for … of
loop instead, in which await
will work as expected:
如果要按顺序读取文件,确实无法使用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);
}
}
Reading in parallel
并行阅读
If you want to read the files in parallel, you cannot use forEach
indeed. Each of the async
callback function calls does return a promise, but you're throwing them away instead of awaiting them. Just use map
instead, and you can await the array of promises that you'll get with Promise.all
:
如果要并行读取文件,则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)
}));
}
回答by Francisco Mateo
With ES2018, you are able to greatly simplify all of the above answers to:
使用 ES2018,您可以大大简化上述所有答案:
async function printFiles () {
const files = await getFilePaths()
for await (const file of fs.readFile(file, 'utf8')) {
console.log(contents)
}
}
See spec: proposal-async-iteration
2018-09-10: This answer has been getting a lot attention recently, please see Axel Rauschmayer's blog post for further information about asynchronous iteration: ES2018: asynchronous iteration
2018-09-10:这个答案最近引起了很多关注,有关异步迭代的更多信息,请参阅Axel Rauschmayer的博客文章:ES2018:异步迭代
回答by Timothy Zorn
Instead of Promise.all
in conjunction with Array.prototype.map
(which does not guarantee the order in which the Promise
s are resolved), I use Array.prototype.reduce
, starting with a resolved Promise
:
而不是Promise.all
结合 with Array.prototype.map
(这不保证Promise
s 的解析顺序),我使用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());
}
回答by Antonio Val
The p-iterationmodule on npm implements the Array iteration methods so they can be used in a very straightforward way with async/await.
npm 上的p-iteration模块实现了 Array 迭代方法,因此它们可以通过 async/await 以非常简单的方式使用。
An example with your case:
你的情况的一个例子:
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);
});
})();
回答by Matt
Here are some forEachAsync
prototypes. Note you'll need to await
them:
这里有一些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));
}
Notewhile you may include this in your own code, you should not include this in libraries you distribute to others (to avoid polluting their globals).
请注意,虽然您可以将其包含在您自己的代码中,但您不应将其包含在您分发给其他人的库中(以避免污染他们的全局变量)。
回答by chharvey
In addition to @Bergi's answer, I'd like to offer a third alternative. It's very similar to @Bergi's 2nd example, but instead of awaiting each readFile
individually, you create an array of promises, each which you await at the end.
除了@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);
}
Note that the function passed to .map()
does not need to be async
, since fs.readFile
returns a Promise object anyway. Therefore promises
is an array of Promise objects, which can be sent to Promise.all()
.
请注意,传递给的函数.map()
不需要是async
,因为fs.readFile
无论如何都会返回一个 Promise 对象。因此promises
是一个 Promise 对象数组,可以发送到Promise.all()
.
In @Bergi's answer, the console may log file contents in the order they're read. For example if a really small file finishes reading before a really large file, it will be logged first, even if the small file comes afterthe large file in the files
array. However, in my method above, you are guaranteed the console will log the files in the same order as the provided array.
在@Bergi 的回答中,控制台可能会按照读取的顺序记录文件内容。例如,如果一个非常小的文件在一个非常大的文件之前完成读取,它将首先被记录,即使小文件在数组中的大文件之后files
。但是,在我上面的方法中,您可以保证控制台将以与提供的数组相同的顺序记录文件。
回答by myDoggyWritesCode
Bergi's solutionworks nicely when fs
is promise based.
You can use bluebird
, fs-extra
or fs-promise
for this.
当fs
基于承诺时,Bergi 的解决方案工作得很好。您可以使用bluebird
,fs-extra
或fs-promise
为此。
However, solution fornode's native fs
libary is as follows:
但是,node原生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;
}));
Note:require('fs')
compulsorily takes function as 3rd arguments, otherwise throws error:
注意:require('fs')
强制将函数作为第三个参数,否则抛出错误:
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
回答by Hooman Askari
Both the solutions above work, however, Antonio's does the job with less code, here is how it helped me resolve data from my database, from several different child refs and then pushing them all into an array and resolving it in a promise after all is done:
上述两种解决方案都有效,但是,Antonio 用更少的代码完成了这项工作,以下是它如何帮助我从我的数据库、几个不同的子引用中解析数据,然后将它们全部推送到一个数组中并在一个 Promise 中解析它毕竟是完毕:
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)))
回答by Jay Edwards
it's pretty painless to pop a couple methods in a file that will handle asynchronous data in a serialized order and give a more conventional flavour to your code. For example:
在一个文件中弹出几个方法非常轻松,这些方法将以序列化的顺序处理异步数据,并为您的代码提供更传统的风格。例如:
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;
};
};
now, assuming that's saved at './myAsync.js' you can do something similar to the below in an adjacent file:
现在,假设它保存在 './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));
}
回答by lukaswilkeer
Like @Bergi's response, but with one difference.
就像@Bergi 的回应一样,但有一点不同。
Promise.all
rejects all promises if one gets rejected.
Promise.all
如果一个被拒绝,则拒绝所有承诺。
So, use a recursion.
所以,使用递归。
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()
PS
聚苯乙烯
readFilesQueue
is outside of printFiles
cause the side effect* introduced by console.log
, it's better to mock, test, and or spy so, it's not cool to have a function that returns the content(sidenote).
readFilesQueue
不是printFiles
由 引入的副作用*的原因之外,console.log
最好是模拟、测试和/或监视,因此,拥有返回内容的函数并不酷(旁注)。
Therefore, the code can simply be designed by that: three separated functions that are "pure"** and introduce no side effects, process the entire list and can easily be modified to handle failed cases.
因此,代码可以简单地设计为:三个分离的“纯粹”**且不引入副作用的函数,处理整个列表并且可以轻松修改以处理失败的情况。
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)
Future edit/current state
未来编辑/当前状态
Node supports top-level await (this doesn't have a plugin yet, won't have and can be enabled via harmony flags), it's cool but doesn't solve one problem (strategically I work only on LTS versions). How to get the files?
Node 支持顶级 await(这还没有插件,不会有并且可以通过和声标志启用),它很酷但不能解决一个问题(策略上我只在 LTS 版本上工作)。如何获取文件?
Using composition. Given the code, causes to me a sensation that this is inside a module, so, should have a function to do it. If not, you should use an IIFE to wrap the role code into an async function creating simple module that's do all for you, or you can go with the right way, there is, composition.
使用组合。给定代码,让我感觉这是在一个模块内,所以,应该有一个函数来做到这一点。如果没有,您应该使用 IIFE 将角色代码包装到一个异步函数中,创建一个为您完成所有工作的简单模块,或者您可以采用正确的方式,即组合。
// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)
Note that the name of variable changes due to semantics. You pass a functor (a function that can be invoked by another function) and recieves a pointer on memory that contains the initial block of logic of the application.
请注意,变量的名称因语义而变化。您传递一个函子(一个可以被另一个函数调用的函数)并在包含应用程序初始逻辑块的内存上接收一个指针。
But, if's not a module and you need to export the logic?
但是,如果不是模块并且您需要导出逻辑?
Wrap the functions in a async function.
将函数包装在异步函数中。
export const readFilesQueue = async () => {
// ... to code goes here
}
Or change the names of variables, whatever...
或者更改变量的名称,无论...
*
by side effect menans any colacteral effect of application that can change the statate/behaviour or introuce bugs in the application, like IO.
*
副作用是指应用程序的任何协同效应,可以改变应用程序的状态/行为或引入应用程序中的错误,如 IO。
**
by "pure", it's in apostrophe since the functions it's not pure and the code can be converged to a pure version, when there's no console output, only data manipulations.
**
通过“纯”,它在撇号中,因为它不是纯函数,并且代码可以收敛到纯版本,当没有控制台输出时,只有数据操作。
Aside this, to be pure, you'll need to work with monads that handles the side effect, that are error prone, and treats that error separately of the application.
除此之外,为了纯粹,您需要使用处理副作用的 monad,它们容易出错,并将该错误与应用程序分开处理。