关于javascript:使用async / await和forEach循环

Using async/await with a forEach loop

forEach循环中使用async/await有什么问题吗?我试图循环访问一组文件,并对每个文件的内容执行await操作。

1
2
3
4
5
6
7
8
9
10
11
12
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将按预期工作:

1
2
3
4
5
6
7
8
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带来的一系列承诺:

1
2
3
4
5
6
7
8
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,可以大大简化在to of the above answers to:P></

1
2
3
4
5
6
7
async function printFiles () {
  const files = await getFilePaths()

  for await (const file of fs.readFile(file, 'utf8')) {
    console.log(contents)
  }
}

规格:EEA HTTPS:tc39 /建议/ / / github.com -异步迭代P></

2018年09月:this has been 10答案让很多关注recently,Please see for further rauschmayer'阿克塞尔的博客信息about异步迭代:http://///asynchronous-iteration.html 2ality.com 2016年10P></


在conjunction instead of Promise.allArray.prototype.map(which does not with the order which the Promises担保是在时间分辨),使用时间分辨Array.prototype.reduce,启动与Promise:P></

1
2
3
4
5
6
7
8
9
10
11
12
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迭代模块实现了数组迭代方法,因此它们可以以非常简单的方式用于异步/等待。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
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);
  });
})();


here are some forEachAsync原型。注释:你awaitneed to themP></

1
2
3
4
5
6
7
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));
}

这可能包括你我的笔记在你自己的代码,你应该包括在你distribute not to this图书馆人(to their avoid polluting全局变量)。P></


在一个文件中弹出两个方法,将以序列化的顺序处理异步数据,并为代码提供一种更传统的风格,这是非常简单的。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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"中,您可以在相邻文件中执行类似于下面的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
...
/* 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));
}


然而,上述两种解决方案都起作用,Antonio的解决方案使用更少的代码来完成这项工作,下面介绍了它如何帮助我从数据库、几个不同的子引用中解析数据,然后将它们全部推送到一个数组中,并在完成所有操作后按承诺进行解析:

1
2
3
4
5
6
7
8
9
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)))

array.foreach currently the prototype属性不支持异步操作,我们可以创造我们自己的,但让我们聚到满足需求。P></

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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安method that are to后arrays定义这些操作。P></

让我们测试它。P></

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 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

我们可以给the same for some of the other阵列映射函数的类。P></

1
2
3
4
5
6
7
8
9
10
11
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

……和我在线:)P></

注释:some things toP></

  • 你iteratorfunction must be an异步函数或承诺
  • 在任何arrays created this will have not available Array.prototype. = 特征

使用task、futureize和可遍历列表,您可以简单地

1
2
3
4
5
6
async function printFiles() {
  const files = await getFiles();

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

这是你如何设置的

1
2
3
4
5
6
7
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)

构造所需代码的另一种方法是

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

或者更注重功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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)

然后从父函数

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

如果您真的想要更灵活的编码,您可以这样做(为了好玩,我正在使用提议的管道转发操作符)

1
2
3
4
5
6
7
8
import { curry, flip } from 'ramda'

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

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

PS-我没有在控制台上尝试此代码,可能有一些拼写错误…"直接的自由式,从穹顶上下来!"正如90年代的孩子们所说。-P


除了@bergi的回答,我还想提供第三种选择。这与@bergi的第二个例子非常相似,但不是单独等待每一个readFile,而是创建一系列承诺,每一个都在最后等待。

1
2
3
4
5
6
7
8
9
10
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);
}

注意,传递给.map()的函数不需要是async,因为fs.readFile无论如何都返回一个promise对象。因此,promises是一组承诺对象,可以发送到Promise.all()上。

在@bergi的回答中,控制台可能会将文件内容记录得不正常。例如,如果一个非常小的文件在一个非常大的文件之前完成读取,那么它将首先被记录,即使这个小文件在files数组中的大文件之后。但是,在上面的方法中,您可以保证控制台将按照与读取文件相同的顺序记录这些文件。


一个重要的警告是:await + for .. of方法和forEach + async方法实际上有不同的效果。

在实际的for循环中使用await将确保所有异步调用都逐个执行。而forEach + async方式将同时兑现所有承诺,速度更快,但有时会不知所措(如果您进行一些数据库查询或访问一些有容量限制的Web服务,并且不希望一次触发100000个呼叫)。

如果您不使用async/await,并且希望确保逐个读取文件,那么也可以使用reduce + promise(不太优雅)。

1
2
3
4
5
files.reduce((lastPromise, file) =>
 lastPromise.then(() =>
   fs.readFile(file, 'utf8')
 ), Promise.resolve()
)

或者您可以创建一个foreachasync来帮助您,但基本上使用相同的for循环底层。

1
2
3
4
5
Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}


val' similar to s p-iteration安东尼奥,安模async-af替代NPM is:P></

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 method has a,静电async-af(日志/日志logaf)that the results of承诺:P></

1
2
3
4
5
6
7
8
9
10
11
12
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();

不管一个人多优势,the main of the Library is that you can do something like to链:异步方法P></

1
2
3
4
5
6
7
8
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-afP></


我将使用测试良好的(每周数百万次下载)PIFY和异步模块。如果您不熟悉异步模块,我强烈建议您查看它的文档。我见过多个devs浪费时间重新创建其方法,或者更糟,当高阶异步方法简化代码时,很难维护异步代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
```