在forEach循环中使用async/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,所以我只想问问这是否有问题。


当前回答

在一个文件中弹出几个方法,以串行化的顺序处理异步数据,并为代码提供更传统的风格,这是非常轻松的。例如:

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

其他回答

与Antonio Val的p迭代类似,另一种npm模块是异步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)来记录promise的结果:

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

异步af

然而,上述两种解决方案都有效,Antonio的代码更少,这是它如何帮助我从数据库中解析数据,从几个不同的子引用中,然后将它们全部推到一个数组中,并在完成所有任务后以承诺的方式进行解析:

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

一个重要的警告是:等待+等待。。of方法和forEach+异步方式实际上有不同的效果。

在真正的for循环中等待将确保所有异步调用都逐一执行。forEach+异步方式将同时发出所有的承诺,这会更快,但有时会不堪重负(如果您执行一些DB查询或访问一些具有容量限制的web服务,并且不想一次发出100000个呼叫)。

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

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

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

const async=要求('async')const fs=要求('s-fromise')const pify=要求('pify')异步函数getFilePaths(){return Promise.resolve(['./“package.json”,'./package-lock.json',]);}异步函数printFiles(){const files=等待getFilePaths()await pify(async.eachSeries)(files,async(file)=>{//<--串联运行//await pify(async.each)(files,async(file)=>{//<--并行运行const contents=await fs.readFile(文件,'utf8')console.log(内容)})console.log('HAMBONE')}printFiles().then(()=>{console.log('HAMBUNY')})//日志顺序://package.json内容//package-lock.json内容//汉堡//汉布尼```

今天,我遇到了多种解决方案。在forEach循环中运行异步等待函数。通过构建包装器,我们可以实现这一点。

在这里的链接中提供了关于它如何在内部工作、对于本机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的泛型函数相同的方法

EachAsync.js的数组.prototype.for

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:传统循环或现代循环

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