首页 文章

使用async / await和forEach循环

提问于
浏览
495

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 所以我只是想问这个是否有任何问题 .

12 回答

  • 2

    而不是 Promise.allArray.prototype.map (不保证 Promise 的解析顺序)一起使用,而是使用 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());
    }
    
  • 0

    对我来说使用带有 map()Promise.all() 有点难以理解和冗长,但是如果你想在普通的JS中做到这一点,我认为这是你最好的镜头 .

    如果您不介意添加模块,我实现了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);
      });
    }
    
    printFiles()
    

    p-iteration

  • 1

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

    或者,async-af有一个静态方法(log / logAF),它记录了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();
    

    但是,该库的主要优点是您可以链接异步方法来执行以下操作:

    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

  • 2

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

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

    我会使用经过充分测试的(每周数百万次下载)pifyasync模块 . 如果您不熟悉异步模块,我强烈建议您查看its docs . 我已经看到多个开发人员浪费时间重新创建它的方法,或者更糟糕的是,当高阶异步方法简化代码时,制作难以维护的异步代码 .

    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
    
  • 13

    使用Task,futurize和一个可遍历的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 operator

    import { curry, flip } from 'ramda'
    
    export const readFile = fs.readFile 
      |> future,
      |> curry,
      |> flip
    
    export const readFileUtf8 = readFile('utf-8')
    

    PS - 我没有在控制台上尝试这个代码,可能会有一些错别字......“直接自由泳,离开圆顶顶部!”正如90年代的孩子们所说的那样 . :-P

  • 1018

    当然代码确实有效,但是我做了你期望它做的事情 . 它只是触发多个异步调用,但 printFiles 函数会在此之后立即返回 .

    如果你想按顺序读取文件,确实是 you cannot use 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);
      }
    }
    

    如果你想并行读取文件,确实是 you cannot use 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)
      }));
    }
    
  • -2

    然而,上面的解决方案都可以解决问题,安东尼奥用更少的代码完成工作,这里是它如何帮助我从数据库中解析数据,从几个不同的子引用,然后将它们全部推入一个数组并在一个承诺中解决它毕竟是完成:

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

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

    在真正的 for 循环中使用 await 将确保所有异步调用逐个执行 . 并且 forEach + async 方式将同时触发所有承诺,这更快但有时不堪重负( if you do some DB query or visit some web services with volume restrictions 并且不希望一次发射100,000个呼叫) .

    如果您不使用 async/await 并且想要确保文件被读取 one after another ,您也可以使用 reduce + promise (不太优雅) .

    files.reduce((lastPromise, file) => 
     lastPromise.then(() => 
       fs.readFile(file, 'utf8')
     ), Promise.resolve()
    )
    

    或者你可以创建一个forEachAsync来帮助,但基本上使用相同的循环底层 .

    Array.prototype.forEachAsync = async function(cb){
        for(let x of this){
            await cb(x);
        }
    }
    
  • 11

    以下是一些forEach异步原型:

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

    使用ES2018,您可以大大简化以上所有答案:

    async function printFiles () {
      const files = await getFilePaths()
    
      for await (const file of fs.readFile(file, 'utf8')) {
        console.log(contents)
      }
    }
    

    见规格:https://github.com/tc39/proposal-async-iteration


    2018-09-10:这个答案最近受到了很多关注,请参阅Axel Rauschmayer的博文,了解有关异步迭代的更多信息:http://2ality.com/2016/10/asynchronous-iteration.html

  • 1

    除了@Bergi’s answer,我还想提供第三种选择 . 它与@ Bergi的第二个例子非常相似,但不是单独等待每个 readFile ,而是创建一个promises数组,每个都在等待最后 .

    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对象数组,可以发送到 Promise.all() .

    在@ Bergi的回答,控制台可能无序地记录文件内容 . 例如,如果一个非常小的文件在一个非常大的文件之前完成读取,它将被首先记录,即使小文件位于 files 数组中的大文件之后 . 但是,在上面的方法中,您可以保证控制台将按照读取的顺序记录文件 .

相关问题