使用async/await和forEach循环

saa*_*adq 864 javascript node.js promise async-await ecmascript-2017

asyncawait循环中使用是否有任何问题?我正在尝试循环遍历文件数组和forEach每个文件的内容.

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()
Run Code Online (Sandbox Code Playgroud)

这段代码确实有效,但这可能会出错吗?我有人告诉我你不应该使用await这样的高阶函数,所以我只是想问一下这是否有任何问题.

Ber*_*rgi 1712

当然代码确实有效,但我很确定它没有按照你的预期去做.它只是触发多个异步调用,但该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);
  }
}
Run Code Online (Sandbox Code Playgroud)

如果要并行读取文件,则无法使用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)
  }));
}
Run Code Online (Sandbox Code Playgroud)

  • 好吧我知道为什么......使用Babel会将`async` /`await`转换为生成器函数,使用`forEach`意味着每次迭代都有一个单独的生成器函数,这与其他函数无关.因此它们将被独立执行,并且没有与其他人一起使用`next()`的上下文.实际上,一个简单的`for()`循环也可以工作,因为迭代也在一个单独的生成器函数中. (64认同)
  • 你能解释一下为什么`......的工作? (28认同)
  • @Demonbane:简而言之,因为它被设计成工作:-)`await`暂停当前的*function*评估,包括所有控制结构.是的,它在这方面非常类似于生成器(这就是为什么它们用于polyfill async/await). (20认同)
  • 当你来学习JS的承诺,而是使用半小时翻译拉丁语.希望你自豪@Bergi;) (5认同)
  • @ arve0不是真的,`async`函数与`Promise` executor回调完全不同,但是`map`回调在两种情况下都返回一个promise. (3认同)
  • 所以 `files.map(async (file) => ...` 相当于 `files.map((file) => new Promise((rej, res) => { ...`)? (2认同)
  • 这个答案是最好的答案:```等待Promise.all(_。map(arr,async(val)=> {...});;)解决了我的问题。当然,每个异步回调都返回一个promise我没有等待。 (2认同)
  • 对于那些不知道的人,[Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)返回一个单独的承诺,当所有承诺解决时数组中的promise已经解决了.(基本上等待所有承诺结束) (2认同)
  • @Taurus如果您不想等待他们,那么“ for…of”将与“ forEach”一样工作。不,我的意思是说那一段是要强调的是,现代JS代码中没有.forEach的位置。 (2认同)
  • 与“forEach”/“map”相比,“for..of”对性能不利,因为“for..of”会在每次迭代中停止,所以如果我发出 3 个请求,每个请求都必须等待前面的请求要求完成。但是使用 `forEach`/`map` 时,所有请求都将并行发出。 (2认同)
  • @AshutoshChamoli只有`async function`调用等待并返回一个promise。“ forEach”并不在乎那个承诺。 (2认同)

Fra*_*teo 146

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

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

  for await (const file of fs.readFile(file, 'utf8')) {
    console.log(contents)
  }
}
Run Code Online (Sandbox Code Playgroud)

请参阅规范:https://github.com/tc39/proposal-async-iteration


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

  • 这个答案是错误的。`files.map()` 返回一个 Promise 数组,**不是一个异步迭代器**,为此创建了 `for wait`![这会导致未处理的拒绝崩溃](/sf/answers/4178707081/)! (13认同)
  • 你的代码中定义了`file`在哪里? (12认同)
  • 人们为什么要赞同这个答案?仔细看看答案,问题和建议.在`of`之后应该是async函数,它将返回一个数组.这不起作用,弗朗西斯科说; (8认同)
  • 它不应该是内容,而不是迭代器中的文件 (5认同)
  • 如果你能在你的答案中为想要了解异步迭代的更多信息的人提供[规范](https://github.com/tc39/proposal-async-iteration)的链接,那么Upvoted会很棒. (3认同)
  • 我不认为这个答案解决了最初的问题。`for-await-of` 与同步迭代(在我们的例子中是一个数组)不包括在每次迭代中使用异步操作并发迭代数组的情况。如果我没记错的话,将 `for-await-of` 与 non-promise 值的同步迭代使用与使用普通的 `for-of` 相同。 (3认同)
  • 完全同意@AntonioVal。这不是答案。 (3认同)
  • 我们如何将 `files` 数组委托给这里的 `fs.readFile`?它从可迭代? (2认同)
  • 虽然我同意这不是一个答案,但支持提案是提高其知名度的一种方法,有可能使其早日可供使用。 (2认同)
  • 此代码无效。需要编辑。 (2认同)
  • 这个答案与OP有同样的问题:它并行访问所有文件。结果的序列化打印只是隐藏了它。 (2认同)

Yil*_*maz 124

files.forEach(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
})
Run Code Online (Sandbox Code Playgroud)

问题是,迭代函数返回的承诺被 忽略forEach()forEach每次异步代码执行完成后,不会等待移动到下一次迭代。所有fs.readFile函数都将在事件循环的同一轮中调用,这意味着它们是并行启动的,而不是顺序启动的,并且在调用 forEach() 后立即继续执行,而无需等待所有操作fs.readFile完成。由于 forEach 不会等待每个 Promise 得到解决,因此循环实际上在 Promise 得到解决之前完成迭代。您期望forEach完成后,所有异步代码都已执行,但事实并非如此。您最终可能会尝试访问尚不可用的值。

您可以使用此示例代码测试行为

files.forEach(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
})
Run Code Online (Sandbox Code Playgroud)

解决方案是使用 for-of 循​​环。

for (const file of files){
    const contents = await fs.readFile(file, 'utf8')
}
Run Code Online (Sandbox Code Playgroud)


Tim*_*orn 52

而不是Promise.allArray.prototype.map(不保证Promises的解析顺序)相结合,我使用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());
}
Run Code Online (Sandbox Code Playgroud)

  • 这非常有效,非常感谢。您能用“Promise.resolve()”和“await Promise;”解释一下这里发生了什么吗? (4认同)
  • @parrker9 `Promise.resolve()` 返回一个已经解析的 `Promise` 对象,因此 `reduce` 有一个 `Promise` 开始。`await promise;` 将等待链中的最后一个 `Promise` 解决。@GollyJer 文件将按顺序处理,一次一个。 (4认同)
  • 如果您需要异步进程尽快完成并且您不关心它们是否按顺序完成,请尝试使用“Promise.all”提供的具有大量赞成票的解决方案之一。示例:`Promise.all(files.map(async (file) => { /* 代码 */ }));` (3认同)
  • 这很酷。我认为这些文件将按顺序读取而不是一次全部读取是否正确? (2认同)
  • @Shay,您的意思是顺序,而不是同步。这仍然是异步的 - 如果安排了其他事情,它们将在此处的迭代之间运行。 (2认同)

小智 38

@Bergi 已经给出了如何正确处理这种特殊情况的答案。我不会在这里重复。

我想解决使用forEachfor循环之间的区别,当涉及到asyncawait

如何forEach运作

让我们看看如何forEach运作。根据ECMAScript 规范,MDN 提供了一个可以用作 polyfill 的实现。我将其复制并粘贴到此处并删除注释。

Array.prototype.forEach = function (callback, thisArg) {
  if (this == null) { throw new TypeError('Array.prototype.forEach called on null or undefined'); }
  var T, k;
  var O = Object(this);
  var len = O.length >>> 0;
  if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function'); }
  if (arguments.length > 1) { T = thisArg; }
  k = 0;
  while (k < len) {
    var kValue;
    if (k in O) {
      kValue = O[k];
      callback.call(T, kValue, k, O); // pay attention to this line
    }
    k++;
  }
};
Run Code Online (Sandbox Code Playgroud)

让我们回到您的代码,让我们将回调提取为函数。

async function callback(file){
  const contents = await fs.readFile(file, 'utf8')
  console.log(contents)
}
Run Code Online (Sandbox Code Playgroud)

所以,基本上callback返回一个承诺,因为它是用 声明的async。Inside forEach,callback只是以正常方式调用,如果回调本身返回一个 Promise,JavaScript 引擎将不会等待它被解析或拒绝。相反,它将放入promise作业队列中,并继续执行循环。

await fs.readFile(file, 'utf8')里面怎么样callback

基本上,当你的异步callback有机会执行时,js 引擎将暂停直到fs.readFile(file, 'utf8')被解析或拒绝,并在完成后恢复执行异步函数。因此该contents变量存储的是 的实际结果fs.readFile,而不是promise. 因此,console.log(contents)注销文件内容而不是Promise

为什么for ... of有效?

当我们编写通用for of循环时,我们获得了比forEach. 让我们重构一下printFiles

async function printFiles () {
  const files = await getFilePaths() // Assume this works fine

  for (const file of files) {
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
    // or await callback(file)
  }
}
Run Code Online (Sandbox Code Playgroud)

当评估for循环时,我们在函数await内部有promise async,执行将暂停,直到awaitpromise被解决。因此,您可以认为文件是按照确定的顺序一个接一个地读取的。

顺序执行

有时,我们确实需要按顺序执行异步函数。例如,我有一些新记录存储在要保存到数据库的数组中,我希望它们按顺序保存,这意味着数组中的第一条记录应首先保存,然后是第二条,直到保存最后一条。

这是一个例子:

const records = [1, 2, 3, 4];

async function saveRecord(record) {
  return new Promise((resolved, rejected) => {
    setTimeout(()=> {
      resolved(`record ${record} saved`)
    }, Math.random() * 500)
  });
}

async function forEachSaveRecords(records) {
  records.forEach(async (record) => {
    const res = await saveRecord(record);
    console.log(res);
  })
}

async function forofSaveRecords(records) {
  for (const record of records) {
    const res = await saveRecord(record);
    console.log(res);
  }
}
(async () => {
  console.log("=== for of save records ===")
  await forofSaveRecords(records)
  
  console.log("=== forEach save records ===")
  await forEachSaveRecords(records)
})()
Run Code Online (Sandbox Code Playgroud)

我用来setTimeout模拟将记录保存到数据库的过程 - 它是异步的并且花费随机时间。使用 时forEach,记录会以不确定的顺序保存,但使用 时for..of,记录会按顺序保存。


Ant*_*Val 29

要我用__CODE____CODE__是有点难以理解和冗长,但是这是我想你最好的镜头,如果你想这样做纯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);
  });
})();
Run Code Online (Sandbox Code Playgroud)

对迭代


Mat*_*att 21

以下是一些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));
}
Run Code Online (Sandbox Code Playgroud)

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

Joh*_*hnz 10

从循环中调用异步方法并不好。这是因为每次循环迭代都会被延迟,直到整个异步操作完成。这不是很高效。async它还避免了/的并行化优势await

更好的解决方案是立即创建所有承诺,然后使用Promise.all(). 否则,在前一个操作完成之前,每个后续操作都不会开始。

因此,代码可以重构如下;

const printFiles = async () => {
  const files = await getFilePaths();
  const results = [];
  files.forEach((file) => {
    results.push(fs.readFile(file, 'utf8'));
  });
  const contents = await Promise.all(results);
  console.log(contents);
}
Run Code Online (Sandbox Code Playgroud)

  • 一次打开数千个文件并同时读取它们也不好。人们总是必须评估顺序、并行或混合方法是否更好。顺序循环本质上并不是坏事,“await”实际上首先使它们成为可能。此外,它们并没有“享受异步执行的好处”,因为您仍然可以一次运行多个此类循环(例如对“printFiles”的两个并发调用)。 (12认同)

ric*_*ong 9

您可以使用Array.prototype.forEach,但 async/await 不太兼容。这是因为从异步回调返回的 Promise 期望得到解析,但Array.prototype.forEach不会解析执行回调时的任何 Promise。那么,您可以使用 forEach,但您必须自己处理承诺解析。

这是一种读取和打印系列中每个文件的方法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
}
Run Code Online (Sandbox Code Playgroud)

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)
}
Run Code Online (Sandbox Code Playgroud)

  • 第一个 senario 非常适合需要串联运行的循环,并且您不能使用 for of (2认同)

Jay*_*rds 8

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

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;
  };
};
Run Code Online (Sandbox Code Playgroud)

现在,假设它保存在 './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));
}
Run Code Online (Sandbox Code Playgroud)

  • 小附录,不要忘记将您的等待/异步包装在 try/catch 块中!! (3认同)

小智 8

只是添加到原来的答案

  • 原始答案中的并行阅读语法有时令人困惑且难以阅读,也许我们可以用不同的方法编写它
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);
}

Run Code Online (Sandbox Code Playgroud)
  • 对于顺序操作,不仅仅是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);
  }
}

Run Code Online (Sandbox Code Playgroud)


Oli*_*xon 8

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

在打字稿中:

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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

如何使用?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})
Run Code Online (Sandbox Code Playgroud)


kru*_*kat 8

图片价值 1000 字 - 仅适用于顺序方法


背景:我昨晚也遇到了类似的情况。我使用 async 函数作为 foreach 参数。结果出乎意料。当我对我的代码进行 3 次测试时,它运行了 2 次没有问题并且失败了 1 次。(奇怪的东西)

最后,我想通了并做了一些便笺簿测试。

场景 1 - 在 foreach 中使用 async 会变得多么不连续

在此处输入图片说明

const getPromise = (time) => { 
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Promise resolved for ${time}s`)
    }, time)
  })
}

const main = async () => {
  const myPromiseArray = [getPromise(1000), getPromise(500), getPromise(3000)]
  console.log('Before For Each Loop')

  myPromiseArray.forEach(async (element, index) => {
    let result = await element;
    console.log(result);
  })

  console.log('After For Each Loop')
}

main();
Run Code Online (Sandbox Code Playgroud)

场景 2 - 使用for - of上面建议的 @Bergi 循环

在此处输入图片说明

const getPromise = (time) => { 
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Promise resolved for ${time}s`)
    }, time)
  })
}

const main = async () => {
  const myPromiseArray = [getPromise(1000), getPromise(500), getPromise(3000)]
  console.log('Before For Each Loop')

  // AVOID USING THIS
  // myPromiseArray.forEach(async (element, index) => {
  //   let result = await element;
  //   console.log(result);
  // })

  // This works well
  for (const element of myPromiseArray) {
    let result = await element;
    console.log(result)
  }

  console.log('After For Each Loop')
}

main();
Run Code Online (Sandbox Code Playgroud)

如果你像我一样有点老派,你可以简单地使用经典的 for 循环,这也有效:)

const getPromise = (time) => { 
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Promise resolved for ${time}s`)
    }, time)
  })
}

const main = async () => {
  const myPromiseArray = [getPromise(1000), getPromise(500), getPromise(3000)]
  console.log('Before For Each Loop')

  // AVOID USING THIS
  // myPromiseArray.forEach(async (element, index) => {
  //   let result = await element;
  //   console.log(result);
  // })

  // This works well too - the classic for loop :)
  for (let i = 0; i < myPromiseArray.length; i++) {
    const result = await myPromiseArray[i];
    console.log(result);
  }

  console.log('After For Each Loop')
}

main();
Run Code Online (Sandbox Code Playgroud)

我希望这对某人有所帮助,美好的一天,干杯!

  • 如果有人想知道 vscode 主题是什么 - 它是 github 的官方 light 主题。如果有人因为如此明亮的快照而伤害了眼睛,我深表歉意 (2认同)
  • 我对这个有点失去了兴趣,但完全值得! (2认同)

Cra*_*cks 8

OP的原始问题

在 forEach 循环中使用 async/await 是否存在任何问题?...

@Bergi选择的答案在一定程度上涵盖了这一点,它展示了如何串行和并行处理。然而,并行性还存在其他问题 -

  1. 命令—— @chharvey指出——

例如,如果一个非常小的文件在一个非常大的文件之前完成读取,则它将首先被记录,即使该小文件位于文件数组中的大文件之后。

  1. 可能一次打开太多文件——Bergi 在另一个答案下的评论

一次打开数千个文件并同时读取它们也不好。人们总是必须评估顺序、并行或混合方法是否更好。

因此,让我们解决这些问题,显示简洁明了的实际代码,并且不使用第三方库。易于剪切、粘贴和修改的东西。

并行读取(一次全部),串行打印(每个文件尽早)。

最简单的改进是像@Bergi 的答案一样执行完全并行,但进行一些小的更改,以便在保留顺序的同时尽快打印每个文件。

async function printFiles2() {
  const readProms = (await getFilePaths()).map((file) =>
    fs.readFile(file, "utf8")
  );
  await Promise.all([
    await Promise.all(readProms),                      // branch 1
    (async () => {                                     // branch 2
      for (const p of readProms) console.log(await p);
    })(),
  ]);
}
Run Code Online (Sandbox Code Playgroud)

上面,两个单独的分支同时运行。

  • 分支 1:并行读取,同时读取,
  • 分支 2:串行读取以强制排序,但等待时间不会超过必要的时间

那很简单。

在并发限制下并行读取,串行打印(每个文件尽早)。

“并发限制”意味着最多只能N同时读取文件。
就像一家商店一次只允许这么多顾客进入(至少在新冠疫情期间)。

首先介绍一个辅助函数 -

function bootablePromise(kickMe: () => Promise<any>) {
  let resolve: (value: unknown) => void = () => {};
  const promise = new Promise((res) => { resolve = res; });
  const boot = () => { resolve(kickMe()); };
  return { promise, boot };
}
Run Code Online (Sandbox Code Playgroud)

该函数bootablePromise(kickMe:() => Promise<any>)采用函数kickMe作为参数来启动任务(在我们的例子中readFile),但不会立即启动。

bootablePromise返回几个属性

  • promise类型的Promise
  • boot类型函数()=>void

promise人生有两个阶段

  1. 作为开始一项任务的承诺
  2. 作为一个承诺,完成已经开始的任务。

promiseboot()调用时从第一状态转换到第二状态。

bootablePromise用于printFiles——

async function printFiles4() {
  const files = await getFilePaths();
  const boots: (() => void)[] = [];
  const set: Set<Promise<{ pidx: number }>> = new Set<Promise<any>>();
  const bootableProms = files.map((file,pidx) => {
    const { promise, boot } = bootablePromise(() => fs.readFile(file, "utf8"));
    boots.push(boot);
    set.add(promise.then(() => ({ pidx })));
    return promise;
  });
  const concurLimit = 2;
  await Promise.all([
    (async () => {                                       // branch 1
      let idx = 0;
      boots.slice(0, concurLimit).forEach((b) => { b(); idx++; });
      while (idx<boots.length) {
        const { pidx } = await Promise.race([...set]);
        set.delete([...set][pidx]);
        boots[idx++]();
      }
    })(),
    (async () => {                                       // branch 2
      for (const p of bootableProms) console.log(await p);
    })(),
  ]);
}
Run Code Online (Sandbox Code Playgroud)

和以前一样有两个分支

  • 分支 1:用于运行和处理并发。
  • 分支2:用于打印

现在的区别是最多concurLimit允许同时运行 Promise。

重要的变量是

  • boots:调用以强制其相应 Promise 转换的函数数组。它仅在分支 1 中使用。
  • set:随机访问容器中有 Promise,一旦实现就可以轻松删除它们。该容器仅在分支 1 中使用。
  • bootableProms:这些与最初的 Promise 相同set,但它是一个数组而不是集合,并且该数组永远不会改变。它仅在分支 2 中使用。

使用模拟运行fs.readFile所需的时间如下(文件名与时间(以毫秒为单位))。

const timeTable = {
  "1": 600,
  "2": 500,
  "3": 400,
  "4": 300,
  "5": 200,
  "6": 100,
};
Run Code Online (Sandbox Code Playgroud)

可以看到这样的测试运行时间,表明并发性正在发挥作用——

[1]0--0.601
[2]0--0.502
[3]0.503--0.904
[4]0.608--0.908
[5]0.905--1.105
[6]0.905--1.005
Run Code Online (Sandbox Code Playgroud)

可在Typescript Playground 沙箱中作为可执行文件使用


myD*_*ode 7

fs基于承诺时,Bergi 的解决方案工作得很好。您可以使用bluebird,fs-extrafs-promise为此。

但是, 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;
    }));
Run Code Online (Sandbox Code Playgroud)

注意: require('fs')强制将函数作为第三个参数,否则抛出错误:

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
Run Code Online (Sandbox Code Playgroud)


luk*_*eer 7

就像@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()
Run Code Online (Sandbox Code Playgroud)

聚苯乙烯

readFilesQueue除了由printFiles引入的副作用*之外console.log,最好进行模拟、测试和/或监视,因此,拥有一个返回内容的函数并不酷(旁注)。

因此,代码可以简单地设计为:三个独立的函数,它们是“纯”**并且不会引入任何副作用,处理整个列表并且可以轻松修改以处理失败的情况。

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)
Run Code Online (Sandbox Code Playgroud)

未来编辑/当前状态

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

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

// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)
Run Code Online (Sandbox Code Playgroud)

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

但是,如果不是模块并且您需要导出逻辑?

将函数包装在异步函数中。

export const readFilesQueue = async () => {
    // ... to code goes here
}
Run Code Online (Sandbox Code Playgroud)

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


*副作用是指应用程序的任何副作用,可以改变应用程序的状态/行为或引入错误,例如 IO。

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

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


Hoo*_*ari 6

上面的两种解决方案都有效,但是,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)))
Run Code Online (Sandbox Code Playgroud)


小智 6

目前 Array.forEach 原型属性不支持异步操作,但我们可以创建自己的 Poly-fill 来满足我们的需求。

// 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}
Run Code Online (Sandbox Code Playgroud)

就是这样!现在,您可以在这些 to 操作之后定义的任何数组上使用 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
Run Code Online (Sandbox Code Playgroud)

我们可以对其他一些数组函数(例如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
Run Code Online (Sandbox Code Playgroud)

... 等等 :)

需要注意的一些事项:

  • 您的 iteratorFunction 必须是异步函数或 Promise
  • 之前创建的任何阵列都Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>不会提供此功能


Pra*_*dro 6

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

有关其内部工作方式的更详细说明,对于本机 forEach 以及为什么它无法进行异步函数调用,以及有关各种方法的其他详细信息在此处的链接中提供

可以通过多种方式完成,如下所示,

方法一:使用包装纸。

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')
           }
         });
     });
    })();
Run Code Online (Sandbox Code Playgroud)

方法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');
            }
        })
      });
    };
  }
Run Code Online (Sandbox Code Playgroud)

用法 :

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
Run Code Online (Sandbox Code Playgroud)

方法三:

使用 Promise.all

  await Promise.all(items.map(async (item) => {
        await someAPICall();
        count++;
    }));

    console.log("count = " + count);
Run Code Online (Sandbox Code Playgroud)

方法 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);
Run Code Online (Sandbox Code Playgroud)


Shu*_*oel 6

您可以像这样使用简单的传统 for 循环

for(let i = 0; i< products.length; i++){
    await <perform some action like database read>
}
Run Code Online (Sandbox Code Playgroud)


LeO*_* Li 5

一个重要的警告是: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()
)
Run Code Online (Sandbox Code Playgroud)

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

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}
Run Code Online (Sandbox Code Playgroud)


chh*_*vey 5

除了@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);
}
Run Code Online (Sandbox Code Playgroud)

请注意,传递给的函数.map()不必是async,因为fs.readFile无论如何都会返回Promise对象。因此,promises是一个Promise对象数组,可以将其发送到Promise.all()

用@Bergi的答案,控制台可以按读取顺序记录文件内容。例如,如果一个很小的文件在一个很大的文件之前完成读取,则即使该小文件在数组中的大文件之后,也将首先记录该文件files。但是,在我上面的方法中,可以确保控制台将以与提供的数组相同的顺序记录文件。


jgm*_*jgm 5

要了解如何出错,请在方法末尾打印 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()
Run Code Online (Sandbox Code Playgroud)

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

这会:

  • 启动所有文件读取并行发生。
  • 通过使用map来映射文件名来保存命令以等待。
  • 按照数组定义的顺序等待每个 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}`);
})();

Run Code Online (Sandbox Code Playgroud)


yea*_*h22 5

一个简单的插入式解决方案用于替换forEach()不工作的await循环替换forEachmap和添加Promise.all(到开始。

例如:

await y.forEach(async (x) => {

await Promise.all(y.map(async (x) => {

)最后需要一个额外的。

  • 不太一样。Promise.all 将_同时_运行所有的 Promise。for 循环意味着是连续的。 (2认同)

归档时间:

查看次数:

375121 次

最近记录:

5 年,9 月 前