在forEach循环中使用异步/等待


1127

在循环中使用async/ 是否有任何问题?我试图遍历文件数组和每个文件的内容。awaitforEachawait

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,所以我只是想问一下这是否有问题。

Answers:


2143

确保代码确实有效,但是我很确定它不会实现您期望的功能。它只会触发多个异步调用,但是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)
  }));
}

33
您能解释一下为什么for ... of ...行得通吗?
Demonbane

84
好的,我知道为什么...使用Babel将变换async/ 转换await为生成器函数,并且使用forEach意味着每次迭代都有一个单独的生成器函数,而与其他迭代器无关。因此它们将独立执行,并且next()与他人没有任何关联。实际上,一个简单的for()循环也是可行的,因为迭代也位于一个生成器函数中。
Demonbane

21
@Demonbane:简而言之,因为它被设计为可以工作:-) await挂起了当前函数评估,包括所有控件结构。是的,在这方面它与生成器非常相似(这就是为什么将它们用于polyfill async / await的原因)。
Bergi

3
@ arve0并非如此,async函数与Promise执行程序回调完全不同,但是是的,map在两种情况下,回调均返回promise。
Bergi

5
当您开始了解JS promise时,可使用半小时翻译拉丁语。希望您为@Bergi感到骄傲;)
-Grenier

187

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

async function printFiles () {
  const files = await getFilePaths()

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

参见规格:proposal-async-iteration


2018-09-10:这个答案最近受到了广泛关注,请参阅Axel Rauschmayer的博客文章以获取有关异步迭代的更多信息:ES2018:异步迭代


4
如果您可以在答案中加入指向规范的链接,对于那些想进一步了解异步迭代的人,这将是很棒的选择。
saadq

8
它不应该是内容,而不是迭代器中的文件
FluffyBeing

10
人们为什么赞成这个答案?仔细看看答案,问题和建议。之后,of应该是异步函数会返回一个阵列。它不起作用,弗朗西斯科说。
Yevhenii Herasymchuk

3
完全同意@AntonioVal。这不是答案。
Yevhenii Herasymchuk

2
虽然我同意这不是一个答案,但支持提案是提高其知名度的一种方式,有可能使其早日可供使用。
罗伯特·莫利纳

61

而不是与Promise.all结合使用Array.prototype.map(这不能保证Promises的解析顺序),我使用Array.prototype.reduce,从solved开始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());
}

1
这非常有效,非常感谢。你能解释一下什么是与发生在这里Promise.resolve()await promise;
parrker9

1
这很酷。我认为文件将被顺序读取而不是一次全部读取是正确的吗?
GollyJer

1
@ parrker9 Promise.resolve()返回一个已经解析的Promise对象,因此reduce必须以a Promise开头。await promise;将等待Promise链中的最后一个解决。@GollyJer文件将被顺序处理,一次处理一次。
Timothy Zorn

使用reduce非常酷,感谢您的评论!我只表示与注释中提到的其他一些方法相比,该方法是同步的,这意味着文件是一个接一个地读取的,而不是并行读取的(因为reduce函数的下一个迭代依赖于上一个迭代,则必须是同步的)。
Shay Yzhakov

1
@Shay,您的意思是顺序的,而不是同步的。这仍然是异步的-如果安排了其他操作,它们将在此处的两次迭代之间运行。
蒂莫西·佐恩

32

npm上的p-iteration模块实现了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);
  });
})();

1
我喜欢它,因为它具有与JS本身相同的功能/方法-就我而言,我需要some而不是forEach。谢谢!
mikemaccana

25

这是一些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));
}

注意尽管您可以在自己的代码中包含此代码,但不应将此内容包含在您分发给其他人的库中(以避免污染其全局变量)。


1
尽管我
不愿意

2
只要名称在将来是唯一的(例如我会使用_forEachAsync),这是合理的。我还认为这是最好的答案,因为它节省了大量样板代码。
mikemaccana

1
@estus这是为了避免污染他人的代码。如果代码属于我们的个人组织,并且全局变量在一个明确标识的文件中(globals.js会很好),我们可以根据需要添加全局变量。
mikemaccana

1
@mikemaccana这是为了避免普遍接受的不良做法。没错,只要您仅使用第一方代码即可完成此操作,这种情况很少发生。问题是,当您使用第三方库时,可能会有其他人感觉相同并修改相同的全局变量,只是因为在编写库时这似乎是个好主意。
Estus Flask,

1
@estus好的。我已在问题中添加了警告,以保存此处的讨论(不是特别有效)。
mikemaccana

6

除了@Bergi的答案外,我还想提供第三个选择。它与@Bergi的第二个示例非常相似,但是您无需readFile创建一个单独的Promise数组,而是每个Promise 都在最后等待。

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。但是,在我上面的方法中,可以确保控制台将以与提供的数组相同的顺序记录文件。


1
我很确定您是不正确的:我很确定您的方法也可以乱读文件。是的,它将以正确的顺序记录输出(由于await Promise.all),但是文件的读取顺序可能不同,这与您的声明“您保证控制台将以与文件相同的顺序记录日志”相矛盾。读”。
Venryx

1
@Venryx您是正确的,感谢您的纠正。我已经更新了答案。
chharvey

5

fs基于承诺时,Bergi的解决方案效果很好。您可以使用bluebirdfs-extrafs-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

4

上面的两个解决方案都可以工作,但是,安东尼奥用更少的代码来完成这项工作,这是它如何帮助我从数据库中解析数据,来自多个不同的子引用,然后将它们全部推入数组并将其解析成一个诺言,毕竟是完成:

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

3

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

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

2
小附录,别忘了将try / catch块中的await / async包装起来!
杰伊·爱德华兹

3

就像@Bergi的回复一样,只是有所不同。

Promise.all 如果一个人被拒绝,那就拒绝所有的承诺。

因此,请使用递归。

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

聚苯乙烯

readFilesQueue不在由printFiles*引起的副作用*console.log最好进行模拟,测试和/或监视,因此,具有返回内容(sidenote)的功能并不酷。

因此,可以通过以下方式简单地设计代码:三个单独的函数,它们是“纯” **的,并且没有副作用,可以处理整个列表,并且可以轻松地修改以处理失败的情况。

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)

未来编辑/当前状态

Node支持顶级等待(它还没有插件,将没有,可以通过和声标志启用),这很酷,但是不能解决一个问题(从策略上讲,我仅在LTS版本上工作)。如何获取文件?

使用组成。给定代码,让我感到这是在模块内部的,因此,应该有一个函数来执行它。如果没有,您应该使用IIFE将角色代码包装到异步函数中,以创建简单的模块来为您完成所有工作,或者您可以采用正确的方式进行组合。

// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)

请注意,变量的名称因语义而变化。您传递一个函子(一个可以被另一个函数调用的函数),并在内存中收到一个指针,该指针包含应用程序的初始逻辑块。

但是,如果不是模块,则需要导出逻辑吗?

将功能包装在异步功能中。

export const readFilesQueue = async () => {
    // ... to code goes here
}

或更改变量名称,无论如何...


* “副作用”表示可能会更改应用程序的状态/行为或引入应用程序错误(例如IO)的任何应用程序协同作用。

** 用“ pure”表示,它是撇号,因为它的功能不纯,并且代码可以收敛为纯版本,当没有控制台输出时,只能进行数据操作。

除此之外,为纯粹起见,您将需要处理容易出错的副作用的monad,并与应用程序分开处理该错误。


2

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

await真实的for循环内部,将确保所有异步调用都被一个接一个地执行。而且该forEach + async方法将同时触发所有的诺言,虽然更快,但有时会不知所措(如果您进行一些数据库查询或访问一些具有音量限制的Web服务,并且不想一次触发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);
    }
}

看看如何在Array.prototype和Object.prototype上的javascript中定义方法,以便它不会出现在for in循环中。同样,您可能应该使用与本机相同的迭代forEach-访问索引而不是依赖可迭代性-并将索引传递给回调。
Bergi

您可以使用Array.prototype.reduce使用异步功能的方式。我在回答中显示了一个示例:stackoverflow.com/a/49499491/2537258
Timothy Zorn,

2

使用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运算符

import { curry, flip } from 'ramda'

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

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

PS-我没有在控制台上尝试此代码,可能会有一些错别字...“直接自由式,位于球顶上方!” 就像90年代的孩子会说的那样。:-p


2

当前,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}

就是这样!现在,在操作之后定义的所有数组上都可以使用async 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>将不具有此功能

2

只是添加到原始答案

  • 原始答案中的并行读取语法有时会令人困惑且难以阅读,也许我们可以采用其他方法编写它
async function printFiles() {
  const files = await getFilePaths();
  const fileReadPromises = [];

  const readAndLogFile = async filePath => {
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
    return contents;
  };

  files.forEach(file => {
    fileReadPromises.push(readAndLogFile(file));
  });

  await Promise.all(fileReadPromises);
}
  • 对于顺序操作,不仅仅是for ... of,正常的for循环也将起作用
async function printFiles() {
  const files = await getFilePaths();

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
  }
}

1

今天,我遇到了多种解决方案。在forEach循环中运行async等待函数。通过围绕包装器,我们可以实现这一目标。

有关本机内部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的泛型函数相同的方法

Array.prototype.forEachAsync.js

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

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

您的方法1和2只是Promise.all应该在何处使用的不正确的实现-它们没有考虑很多边缘情况。
Bergi

@Bergi:感谢您的有效评论,请您解释一下为什么方法1和2不正确。它也可以达到目的。这很好。就是说,所有这些方法都是可能的,基于一种情况可以决定选择一种。我有相同的运行示例。
PranavKAndro

它在空数组上失败,没有任何错误处理,可能还有更多问题。不要重新发明轮子。只需使用Promise.all
Bergi

在某些不可能的情况下会有所帮助。默认情况下,forEach api也进行错误处理,因此没有问题。它的照顾!
PranavKAndro,

没有,没有条件在那里Promise.all是不可能的,但是async/ await是。不,forEach绝对不处理任何承诺错误。
Bergi

1

该解决方案还对内存进行了优化,因此您可以在10,000个数据项和请求上运行它。这里的其他一些解决方案会使服务器在大型数据集上崩溃。

在TypeScript中:

export async function asyncForEach<T>(array: Array<T>, callback: (item: T, index: number) => void) {
        for (let index = 0; index < array.length; index++) {
            await callback(array[index], index);
        }
    }

如何使用?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})

1

您可以使用Array.prototype.forEach,但是async / await不太兼容。这是因为从异步回调返回的promise有望得到解决,但是Array.prototype.forEach不会从其回调的执行中解决任何promise。因此,您可以使用forEach,但是您必须自己处理promise的解决方案。

这是一种使用以下方式连续读取和打印每个文件的方法 Array.prototype.forEach

async function printFilesInSeries () {
  const files = await getFilePaths()

  let promiseChain = Promise.resolve()
  files.forEach((file) => {
    promiseChain = promiseChain.then(() => {
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    })
  })
  await promiseChain
}

这是一种(仍然使用Array.prototype.forEach)并行打印文件内容的方法

async function printFilesInParallel () {
  const files = await getFilePaths()

  const promises = []
  files.forEach((file) => {
    promises.push(
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    )
  })
  await Promise.all(promises)
}

第一个senario非常适合需要在意甲中运行的循环,您不能使用它
Mark Odey

0

与Antonio Val's类似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)记录承诺的结果:

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


0

若要查看如何解决问题,请在方法末尾打印console.log。

通常会出错的事情:

  • 任意顺序。
  • printFiles可以在打印文件之前完成运行。
  • 表现不佳。

这些并不总是错误的,但在标准用例中经常如此。

通常,使用forEach将导致除最后一个以外的所有结果。它会在不等待函数的情况下调用每个函数,这意味着它告诉所有函数启动然后在不等待函数完成的情况下完成。

import fs from 'fs-promise'

async function printFiles () {
  const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'))

  for(const file of files)
    console.log(await file)
}

printFiles()

这是本机JS中的一个示例,它将保留顺序,防止函数过早返回,并且在理论上保留最佳性能。

这将:

  • 初始化所有文件读取以并行发生。
  • 通过使用map将文件名映射到promises来保留命令。
  • 等待数组定义的顺序中的每个promise。

使用此解决方案,将在第一个文件可用时立即显示它,而不必等待其他文件首先可用。

它还将同时加载所有文件,而不必等待第一个文件完成才可以开始读取第二个文件。

此版本和原始版本的唯一缺点是,如果一次开始多次读取,则由于一次可能发生更多错误,因此更难处理错误。

使用一次读取一个文件的版本,然后将在发生故障时停止,而不会浪费时间尝试读取更多文件。即使使用精心设计的取消系统,也很难避免它在第一个文件上失败,但同时也已经读取了大多数其他文件。

性能并非总是可预测的。尽管许多系统使用并行文件读取会更快,但有些系统会更喜欢顺序读取。有些是动态的,可能在负载下发生变化,提供延迟的优化在争用激烈的情况下并不总是能产生良好的吞吐量。

该示例中也没有错误处理。如果某些事情要求它们要么全部成功显示,要么根本不显示,那就不会了。

建议在每个阶段使用console.log进行深入实验,并使用伪造的文件读取解决方案(改为随机延迟)。尽管许多解决方案在简单情况下似乎都执行相同的操作,但所有解决方案都有细微的差异,因此需要进行额外的审查才能挤出。

使用此模拟可以帮助说明解决方案之间的区别:

(async () => {
  const start = +new Date();
  const mock = () => {
    return {
      fs: {readFile: file => new Promise((resolve, reject) => {
        // Instead of this just make three files and try each timing arrangement.
        // IE, all same, [100, 200, 300], [300, 200, 100], [100, 300, 200], etc.
        const time = Math.round(100 + Math.random() * 4900);
        console.log(`Read of ${file} started at ${new Date() - start} and will take ${time}ms.`)
        setTimeout(() => {
          // Bonus material here if random reject instead.
          console.log(`Read of ${file} finished, resolving promise at ${new Date() - start}.`);
          resolve(file);
        }, time);
      })},
      console: {log: file => console.log(`Console Log of ${file} finished at ${new Date() - start}.`)},
      getFilePaths: () => ['A', 'B', 'C', 'D', 'E']
    };
  };

  const printFiles = (({fs, console, getFilePaths}) => {
    return async function() {
      const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'));

      for(const file of files)
        console.log(await file);
    };
  })(mock());

  console.log(`Running at ${new Date() - start}`);
  await printFiles();
  console.log(`Finished running at ${new Date() - start}`);
})();

-3

我将使用经过严格测试的(每周数百万次下载)pify异步模块。如果您不熟悉异步模块,强烈建议您查看其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
```


这是朝错误方向迈出的一步。下面是创建,以帮助人们映射导游,我被困在地狱回调进入现代化时代JS:github.com/jmjpro/async-package-to-async-await/blob/master/...
jbustamovej

如您在这里看到的,我很感兴趣并且愿意使用async / await代替async lib。现在,我认为每个人都有时间和地点。我不相信异步lib ==“回调地狱”和async / await ==“现代JS时代”。imo,当async lib> async / await时:1.复杂的流程(例如,当事情变得复杂时,队列,货物,甚至自动运行)2.并发3.支持数组/对象/可迭代对象4. err处理
Zachary Ryan Smith
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.