使用异步/等待foreach循环

发布于 2025-01-17 13:54:19 字数 481 浏览 4 评论 0 原文

在 foreach 循环中,使用 async /是否有任何问题?我正在尝试在每个文件的内容上循环浏览一系列文件和等待。

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 /等待,所以我只想问是否有任何问题与此。

Are there any issues with using async/await in a forEach loop? I'm trying to loop through an array of files and await on the contents of each file.

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

This code does work, but could something go wrong with this? I had someone tell me that you're not supposed to use async/await in a higher-order function like this, so I just wanted to ask if there was any issue with this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

小矜持 2025-01-24 13:54:19

当然,代码确实有效,但是我敢肯定,它不会执行您期望的事情。它只是启动了多个异步调用,但是 printfiles 函数在此之后立即返回。

按顺序读取,

如果您想按顺序读取文件,确实不能使用 foreach 。只需使用现代来取... 循环,其中等待就可以按预期工作:

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

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

阅读

如果您想并行读取文件,则并行 ,您不能确实使用 确实使用。每个 async 回调函数呼叫确实会返回承诺,但是您将它们扔掉而不是等待它们。只需使用 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)
  }));
}

Sure the code does work, but I'm pretty sure it doesn't do what you expect it to do. It just fires off multiple asynchronous calls, but the printFiles function does immediately return after that.

Reading in sequence

If you want to read the files in sequence, you cannot use forEach indeed. Just use a modern for … of loop instead, in which await will work as expected:

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

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

Reading in parallel

If you want to read the files in parallel, you cannot use forEach indeed. Each of the async callback function calls does return a promise, but you're throwing them away instead of awaiting them. Just use map instead, and you can await the array of promises that you'll get with 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)
  }));
}
那些过往 2025-01-24 13:54:19

借助ES2018,您可以大大简化上述所有答案:

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

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

参见规格:

简化:

  for await (const results of array) {
    await longRunningTask()
  }
  console.log('I will wait')

2018-09-10:这个答案最近引起了很多关注,请参阅 Axel Rauschmayer的博客文章有关异步迭代的更多信息。

With ES2018, you are able to greatly simplify all of the above answers to:

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

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

See spec: proposal-async-iteration

Simplified:

  for await (const results of array) {
    await longRunningTask()
  }
  console.log('I will wait')

2018-09-10: This answer has been getting a lot of attention recently, please see Axel Rauschmayer's blog post for further information about asynchronous iteration.

遮了一弯 2025-01-24 13:54:19

而不是 promise.all array.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());
}

Instead of Promise.all in conjunction with Array.prototype.map (which does not guarantee the order in which the Promises are resolved), I use Array.prototype.reduce, starting with a resolved 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());
}
音栖息无 2025-01-24 13:54:19
  files.foreach(async(file)=> {
    const contents =等待fs.ReadFile(文件,'utf8')
}))
 

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

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

const array = [1, 2, 3];

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const delayedSquare = (num) => sleep(100).then(() => num * num);

const testForEach = (numbersArray) => {
  const store = [];
  // this code here treated as sync code
  numbersArray.forEach(async (num) => {
    const squaredNum = await delayedSquare(num);
    // this will console corrent squaredNum value
    // console.log(squaredNum) will log after console.log("store",store)
    console.log(squaredNum);
    store.push(squaredNum);
  });
  // you expect that store array is populated as [1,4,9] but it is not
  // this will return []
  console.log("store",store);
};
testForEach(array);
// Notice, when you test, first "store []" will be logged
// then squaredNum's inside forEach will log

该解决方案使用的是循环。

for (const file of files){
    const contents = await fs.readFile(file, 'utf8')
}
files.forEach(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
})

The issue is, the promise returned by the iteration function is ignored by forEach(). forEach does not wait to move to the next iteration after each async code execution is completed. All the fs.readFile functions
will be invoked in the same round of the event loop, which means they are started in parallel, not in sequential, and the execution continues immediately after invoking forEach(), without
waiting for all the fs.readFile operations to complete. Since forEach does not wait for each promise to resolve, the loop actually finishes iterating before promises are resolved. You are expecting that after forEach is completed, all the async code is already executed but that is not the case. You may end up trying to access values that are not available yet.

you can test the behaviour with this example code

const array = [1, 2, 3];

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const delayedSquare = (num) => sleep(100).then(() => num * num);

const testForEach = (numbersArray) => {
  const store = [];
  // this code here treated as sync code
  numbersArray.forEach(async (num) => {
    const squaredNum = await delayedSquare(num);
    // this will console corrent squaredNum value
    // console.log(squaredNum) will log after console.log("store",store)
    console.log(squaredNum);
    store.push(squaredNum);
  });
  // you expect that store array is populated as [1,4,9] but it is not
  // this will return []
  console.log("store",store);
};
testForEach(array);
// Notice, when you test, first "store []" will be logged
// then squaredNum's inside forEach will log

the solution is using the for-of loop.

for (const file of files){
    const contents = await fs.readFile(file, 'utf8')
}
九八野马 2025-01-24 13:54:19

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


背景:我昨晚处于类似情况。我将异步函数用作for for for for Angrage。结果是不可预测的。当我对代码进行了3次测试时,它没有问题2次,失败了1次。 (有些奇怪的是)

终于我四处走动&进行了一些刮擦测试。

中,它可以使用异步添加效率。

方案1-在foreach

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

方案2-使用 for -of -of loop as @bergi as @bergi as @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();

i.sstatic.net/qwbri.png“ rel =“ noreferrer 像我一样小的老派,您可以简单地将经典循环使用,也可以使用:)

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

我希望这对某人有帮助,美好的一天,欢呼!

Picture worth 1000 words - For Sequential Approach Only


Background : I was in similar situation last night. I used async function as foreach argument. The result was un-predictable. When I did testing for my code 3 times, it ran without issues 2 times and failed 1 time. (something weird)

Finally I got my head around & did some scratch pad testing.

Scenario 1 - How un-sequential it can get with async in foreach

enter image description here

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

Scenario 2 - Using for - of loop as @Bergi above suggested

enter image description here

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

If you are little old school like me, you could simply use the classic for loop, that works too :)

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

I hope this helps someone, good day, cheers!

北渚 2025-01-24 13:54:19

p-titeration npm上的模块实现了数组迭代方法,因此可以在非常直接的情况下使用它们与异步/等待的方式。

您的案件的一个例子:

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

The p-iteration module on npm implements the Array iteration methods so they can be used in a very straightforward way with async/await.

An example with your case:

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);
  });
})();
渡你暖光 2025-01-24 13:54:19

这是一些 foreachAsync 原型。请注意,您需要等待他们:

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

Note ,您可能将其包含在您自己的代码中,但您不应将其包含在将其分发给他人的库中(以避免污染他们的代码全球)。

要使用:

await myArray. forEachAsyncParallel( async (item) => { await myAsyncFunction(item) })

请注意,您也需要一个现代化的 esrun 才能在打字稿中的最高级别使用。

Here are some forEachAsync prototypes. Note you'll need to await them:

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

Note while you may include this in your own code, you should not include this in libraries you distribute to others (to avoid polluting their globals).

To use:

await myArray. forEachAsyncParallel( async (item) => { await myAsyncFunction(item) })

Note you'll need a modern too like esrun to use await at the top level in TypeScript.

聚集的泪 2025-01-24 13:54:19

@Bergi已经给出了有关如何正确处理此特定情况的答案。我不会在这里复制。

我想解决 for 和循环的之间的

区别strong> <代码> foreach works

让我们看一下 foreach 的工作方式。根据 ecmascript Specificity a href =“ https://developer.mozilla.org/en-us/docs/web/javascript/reference/reference/global_objects/array/aray/foreach#polyfill” rel =“ noreferrer”>多填充。我将其复制并在此处粘贴,并删除评论。

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

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

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

因此,基本上是回调返回承诺,因为它用 async 声明。内部 foreach 回调只是以正常方式调用,如果回调本身返回承诺,则JavaScript引擎将不等待其解决或拒绝。相反,它将 Promise 放在作业队列中,并继续执行循环。

等待fs.ReadFile(file,'utf8')回调>回调

基本上,当您的async callback 有机会被执行,JS引擎将暂停,直到 fs.ReadFile(file,'utf8')被解析或拒绝,并在实现后恢复执行异步功能。因此,内容变量存储 fs.ReadFile 的实际结果,而不是 Promise 。因此, console.log(contents)记录文件内容而不是 Promise

为什么 for ... of 有效?

当我们为Of 循环编写通用时,我们将获得比 的控制权更多。让我们重构 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)
  }
}

当评估循环时,我们有等待保证 async 函数,执行将暂停,直到等待已解决。因此,您可以认为文件是按确定的顺序逐一读取的。

有时会顺序执行

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

这是一个示例:

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

我使用 settimeout 来模拟将记录保存到数据库的过程 - 它是异步的,并且花费了随机的时间。使用 foreach ,记录以不确定的顺序保存,但使用 使用,它们被顺序保存。

@Bergi has already gave the answer on how to handle this particular case properly. I'll not duplicate here.

I'd like to address the difference between using forEach and for loop when it comes to async and await

how forEach works

Let's look at how forEach works. According to ECMAScript Specification, MDN provides an implementation which can be used as a polyfill. I copy it and paste here with comments removal.

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

Let's back to your code, let's extract the callback as a function.

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

So, basically callback returns a promise since it's declared with async. Inside forEach, callback is just called in a normal way, if the callback itself returns a promise, the javascript engine will not wait it to be resolved or rejected. Instead, it puts the promise in a job queue, and continues executing the loop.

How about await fs.readFile(file, 'utf8') inside the callback?

Basically, when your async callback gets the chance to be executed, the js engine will pause until fs.readFile(file, 'utf8') to be resolved or rejected, and resume execution of the async function after fulfillment. So the contents variable store the actual result from fs.readFile, not a promise. So, console.log(contents) logs out the file content not a Promise

Why for ... of works?

when we write a generic for of loop, we gain more control than forEach. Let's refactor 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)
  }
}

When evaluate for loop, we have await promise inside the async function, the execution will pause until the await promise is settled. So, you can think of that the files are read one by one in a determined order.

Execute sequentially

Sometimes, we really need the the async functions to be executed in a sequential order. For example, I have a few new records stored in an array to be saved to database, and I want them to be saved in sequential order which means first record in array should be saved first, then second, until last one is saved.

Here is an example:

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

I use setTimeout to simulate the process of saving a record to database - it's asynchronous and cost a random time. Using forEach, the records are saved in an undetermined order, but using for..of, they are saved sequentially.

人生戏 2025-01-24 13:54:19

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

在 TypeScript 中:

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

如何使用?

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

This solution is also memory-optimized so you can run it on 10,000's of data items and requests. Some of the other solutions here will crash the server on large data sets.

In TypeScript:

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

How to use?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})
┼── 2025-01-24 13:54:19

替换不起作用的 forEach() wait 循环的一个简单的嵌入式解决方案是将 forEach 替换为 map 并添加 Promise .all( 到开头。

例如:

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

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

最后需要额外的 )

A simple drop-in solution for replacing a forEach() await loop that is not working is replacing forEach with map and adding Promise.all( to the beginning.

For example:

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

to

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

An extra ) is needed at the end.

傲性难收 2025-01-24 13:54:19

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

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

In addition to @Bergi’s answer, I’d like to offer a third alternative. It's very similar to @Bergi’s 2nd example, but instead of awaiting each readFile individually, you create an array of promises, each which you await at the end.

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

Note that the function passed to .map() does not need to be async, since fs.readFile returns a Promise object anyway. Therefore promises is an array of Promise objects, which can be sent to Promise.all().

In @Bergi’s answer, the console may log file contents in the order they’re read. For example if a really small file finishes reading before a really large file, it will be logged first, even if the small file comes after the large file in the files array. However, in my method above, you are guaranteed the console will log the files in the same order as the provided array.

隱形的亼 2025-01-24 13:54:19

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

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

it's pretty painless to pop a couple methods in a file that will handle asynchronous data in a serialized order and give a more conventional flavour to your code. For example:

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

now, assuming that's saved at './myAsync.js' you can do something similar to the below in an adjacent file:

...
/* 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));
}
塔塔猫 2025-01-24 13:54:19

fs 基于 Promise 时,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

Bergi's solution works nicely when fs is promise based.
You can use bluebird, fs-extra or fs-promise for this.

However, solution for node's native fs libary is as follows:

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

Note:
require('fs') compulsorily takes function as 3rd arguments, otherwise throws error:

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
春风十里 2025-01-24 13:54:19

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

更好的解决方案是一次创建所有 Promise,然后使用 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);
}

It is not good to call an asynchronous method from a loop. This is because each loop iteration will be delayed until the entire asynchronous operation completes. That is not very performant. It also averts the advantages of parallelization benefits of async/await.

A better solution would be to create all promises at once, then get access to the results using Promise.all(). Otherwise, each successive operation will not start until the previous one has completed.

Consequently, the code may be refactored as follows;

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);
}
看海 2025-01-24 13:54:19

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

在真正的 for 循环中使用 await 将确保所有异步调用都一一执行。 forEach + async 方式会同时触发所有的 Promise,这种方式速度更快,但有时会不知所措(如果你进行一些数据库查询或访问一些有流量限制的 Web 服务 并且不想一次发出 100,000 个呼叫)。

如果您不使用 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);
    }
}

One important caveat is: The await + for .. of method and the forEach + async way actually have different effect.

Having await inside a real for loop will make sure all async calls are executed one by one. And the forEach + async way will fire off all promises at the same time, which is faster but sometimes overwhelmed(if you do some DB query or visit some web services with volume restrictions and do not want to fire 100,000 calls at a time).

You can also use reduce + promise(less elegant) if you do not use async/await and want to make sure files are read one after another.

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

Or you can create a forEachAsync to help but basically use the same for loop underlying.

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}
给妤﹃绝世温柔 2025-01-24 13:54:19

您可以使用 array.prototype.foreach ,但是异步/等待不是那么兼容。这是因为从异步回调返回的承诺期望得到解决,但是 array.prototype.foreach 无法从执行其回调中解决任何承诺。因此,您可以使用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)
}

You can use Array.prototype.forEach, but async/await is not so compatible. This is because the promise returned from an async callback expects to be resolved, but Array.prototype.forEach does not resolve any promises from the execution of its callback. So then, you can use forEach, but you'll have to handle the promise resolution yourself.

Here is a way to read and print each file in series using 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
}

Here is a way (still using Array.prototype.forEach) to print the contents of files in parallel

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)
}
撕心裂肺的伤痛 2025-01-24 13:54:19

只是添加到原始答案

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

Just adding to the original answer

  • The parallel reading syntax in the original answer is sometimes confusing and difficult to read, maybe we can write it in a different approach
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 sequential operation, not just for...of, normal for loop will also work
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);
  }
}

初懵 2025-01-24 13:54:19

OP的原始问题

在foreach循环中使用异步/等待异步有什么问题? ...

在 @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);
    })(),
  ]);
}

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

  • 分支1:同时同时读取,
  • 分支2:以序列为止读取序列,但等待不超过必要的时间

很容易。

与并发限制并行读取,以串行打印(尽可能尽早)。

“并发限制”意味着不超过 n 文件。
就像一家仅允许一次允许如此多客户(至少在Covid期间)的商店。

首先,引入了辅助功能

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

-Code> Bootable Promise(Kickme :()=&gt; promise&lt; any&gt;)
函数 kickme 作为启动任务的参数(在我们的情况下 readfile ),但没有立即启动。

Bootable Promise 返回一个属性

  • promise type promise
  • boot type function type function ()=&gt; void

promise 在生活中有两个阶段

  1. 是启动任务的承诺
  2. ,成为承诺完成已经开始的任务。

Promise 在调用 boot()时从第一个状态到第二个状态。

bootable Promise 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);
    })(),
  ]);
}

就像之前有两个分支

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

现在要打印差异,不得超过共同限制承诺可以同时运行。

重要的变量是

  • boots :呼吁强制其相应过渡的函数的数组。它仅在分支1中使用。
  • set :随机访问容器中有保证,以便一旦实现,它们就可以轻松删除。该容器仅在分支1中使用。
  • BootableProm :这些与最初的承诺相同。它仅在分支2中使用。

使用模拟 fs.ReadFile 运行,该需要以下时间(文件名与MS中的时间)。

const timeTable = {
  "1": 600,
  "2": 500,
  "3": 400,
  "4": 300,
  "5": 200,
  "6": 100,
};

test run times such as this are seen, showing the concurrency is working --

[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

Available as executable in the typescript playground sandbox

The OP's original question

Are there any issues with using async/await in a forEach loop? ...

was covered to an extent in @Bergi's selected answer,
which showed how to process in serial and in parallel. However there are other issues noted with parallelism -

  1. Order -- @chharvey notes that -

For example if a really small file finishes reading before a really large file, it will be logged first, even if the small file comes after the large file in the files array.

  1. Possibly opening too many files at once -- A comment by Bergi under another answer

It is also not good to open thousands of files at once to read them concurrently. One always has to do an assessment whether a sequential, parallel, or mixed approach is better.

So let's address these issues showing actual code that is brief and concise, and does not use third party libraries. Something easy to cut, paste, and modify.

Reading in parallel (all at once), printing in serial (as early as possible per file).

The easiest improvement is to perform full parallelism as in @Bergi's answer, but making a small change so that each file is printed as soon as possible while preserving order.

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);
    })(),
  ]);
}

Above, two separate branches are run concurrently.

  • branch 1: Reading in parallel, all at once,
  • branch 2: Reading in serial to force order, but waiting no longer than necessary

That was easy.

Reading in parallel with a concurrency limit, printing in serial (as early as possible per file).

A "concurrency limit" means that no more than N files will ever being read at the same time.
Like a store that only allows in so many customers at a time (at least during COVID).

First a helper function is introduced -

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

The function bootablePromise(kickMe:() => Promise<any>) takes a
function kickMe as an argument to start a task (in our case readFile) but is not started immediately.

bootablePromise returns a couple of properties

  • promise of type Promise
  • boot of type function ()=>void

promise has two stages in life

  1. Being a promise to start a task
  2. Being a promise complete a task it has already started.

promise transitions from the first to the second state when boot() is called.

bootablePromise is used in 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);
    })(),
  ]);
}

As before there are two branches

  • branch 1: For running and handling concurrency.
  • branch 2: For printing

The difference now is the no more than concurLimit Promises are allowed to run concurrently.

The important variables are

  • boots: The array of functions to call to force its corresponding Promise to transition. It is used only in branch 1.
  • set: There are Promises in a random access container so that they can be easily removed once fulfilled. This container is used only in branch 1.
  • bootableProms: These are the same Promises as initially in set, but it is an array not a set, and the array is never changed. It is used only in branch 2.

Running with a mock fs.readFile that takes times as follows (filename vs. time in ms).

const timeTable = {
  "1": 600,
  "2": 500,
  "3": 400,
  "4": 300,
  "5": 200,
  "6": 100,
};

test run times such as this are seen, showing the concurrency is working --

[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

Available as executable in the typescript playground sandbox

凉栀 2025-01-24 13:54:19

您可以将简单的传统用于这样的循环

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

You can use a simple traditional for loop like this

for(let i = 0; i< products.length; i++){
    await <perform some action like database read>
}
我只土不豪 2025-01-24 13:54:19

但是,上述解决方案的两种解决方案都可以用更少的代码来完成这项工作,这是它如何帮助我从数据库,几个不同的儿童refs解决数据,然后将它们全部推入数组,然后将其置于诺言中,毕竟是完毕:

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

Both the solutions above work, however, Antonio's does the job with less code, here is how it helped me resolve data from my database, from several different child refs and then pushing them all into an array and resolving it in a promise after all is done:

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)))
吖咩 2025-01-24 13:54:19

就像 @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()

ps

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)

未来编辑/当前状态

节点支持顶级等待(这还没有插件,还没有和可以通过Harmony标志启用),很酷,但不能解决一个问题(从策略上我仅在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
}

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


* by Side效果Menans的应用程序效果可以更改应用程序中的雕像/行为或Intouce错误,例如IO。

** by“纯”,它在撇号中,因为函数不是纯净的,并且当没有控制台输出时,只有数据操作就可以将代码收集到纯版本。

除此之外,要纯粹,您需要使用处理副作用的单子,这些副作用容易出错,并分别处理该应用程序的错误。

Like @Bergi's response, but with one difference.

Promise.all rejects all promises if one gets rejected.

So, use a recursion.

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

PS

readFilesQueue is outside of printFiles cause the side effect* introduced by console.log, it's better to mock, test, and or spy so, it's not cool to have a function that returns the content(sidenote).

Therefore, the code can simply be designed by that: three separated functions that are "pure"** and introduce no side effects, process the entire list and can easily be modified to handle failed cases.

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)

Future edit/current state

Node supports top-level await (this doesn't have a plugin yet, won't have and can be enabled via harmony flags), it's cool but doesn't solve one problem (strategically I work only on LTS versions). How to get the files?

Using composition. Given the code, causes to me a sensation that this is inside a module, so, should have a function to do it. If not, you should use an IIFE to wrap the role code into an async function creating simple module that's do all for you, or you can go with the right way, there is, composition.

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

Note that the name of variable changes due to semantics. You pass a functor (a function that can be invoked by another function) and recieves a pointer on memory that contains the initial block of logic of the application.

But, if's not a module and you need to export the logic?

Wrap the functions in a async function.

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

Or change the names of variables, whatever...


* by side effect menans any colacteral effect of application that can change the statate/behaviour or introuce bugs in the application, like IO.

** by "pure", it's in apostrophe since the functions it's not pure and the code can be converged to a pure version, when there's no console output, only data manipulations.

Aside this, to be pure, you'll need to work with monads that handles the side effect, that are error prone, and treats that error separately of the application.

久伴你 2025-01-24 13:54:19

目前 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}

就是这样!现在,您可以在这些 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

我们可以对其他一些数组函数(例如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. = 将不提供此功能

Currently the Array.forEach prototype property doesn't support async operations, but we can create our own poly-fill to meet our needs.

// 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}

And that's it! You now have an async forEach method available on any arrays that are defined after these to operations.

Let's test it...

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

We could do the same for some of the other array functions like 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

... and so on :)

Some things to note:

  • Your iteratorFunction must be an async function or promise
  • Any arrays created before Array.prototype.<yourAsyncFunc> = <yourAsyncFunc> will not have this feature available
撕心裂肺的伤痛 2025-01-24 13:54:19

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

有关其工作原理的更详细说明在内部,对于本机 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);

Today I came across multiple solutions for this. Running the async await functions in the forEach Loop. By building the wrapper around we can make this happen.

More detailed explanation on how it works internally, for the native forEach and why it is not able to make a async function call and other details on the various methods are provided in link here

The multiple ways through which it can be done and they are as follows,

Method 1 : Using the wrapper.

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

Method 2: Using the same as a generic function of 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');
            }
        })
      });
    };
  }

Usage :

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

Method 3 :

Using Promise.all

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

    console.log("count = " + count);

Method 4 : Traditional for loop or modern for loop

// 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);
誰ツ都不明白 2025-01-24 13:54:19

要查看如何出错,请在方法末尾打印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中的一个示例,它将保留顺序,防止函数过早返回,从理论上讲,该功能保留了最佳性能。

这将:

  • 启动所有文件读取的所有文件都在并行发生。
  • 通过使用地图将文件名映射到保证等待的订单。
  • 在数组定义的顺序中等待每个承诺。

使用此解决方案,第一个文件将在可用的情况下立即显示,而无需等待其他文件首先可用。

它还将同时加载所有文件,而不必等待第一个文件才能完成,才能启动第二个文件读取。

唯一的回报和原始版本是,如果一次启动了多个读数,那么由于有更多的错误可能发生的错误,因此很难处理错误。

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

性能并非总是可以预见的。尽管许多系统将使用并行文件读取速度更快,但有些系统更喜欢顺序。有些是动态的,可能会在负载下转移,提供潜伏期的优化并不总是在重大争论下产生良好的吞吐量。

在该示例中也没有错误处理。如果某事要求它们成功地显示或根本不表现出来,则不会做到这一点。

在每个阶段,建议使用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}`);
})();

To see how that can go wrong, print console.log at the end of the method.

Things that can go wrong in general:

  • Arbitrary order.
  • printFiles can finish running before printing files.
  • Poor performance.

These are not always wrong but frequently are in standard use cases.

Generally, using forEach will result in all but the last. It'll call each function without awaiting for the function meaning it tells all of the functions to start then finishes without waiting for the functions to finish.

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

This is an example in native JS that will preserve order, prevent the function from returning prematurely and in theory retain optimal performance.

This will:

  • Initiate all of the file reads to happen in parallel.
  • Preserve the order via the use of map to map file names to promises to wait for.
  • Wait for each promise in the order defined by the array.

With this solution the first file will be shown as soon as it is available without having to wait for the others to be available first.

It will also be loading all files at the same time rather than having to wait for the first to finish before the second file read can be started.

The only draw back of this and the original version is that if multiple reads are started at once then it's more difficult to handle errors on account of having more errors that can happen at a time.

With versions that read a file at a time then then will stop on a failure without wasting time trying to read any more files. Even with an elaborate cancellation system it can be hard to avoid it failing on the first file but reading most of the other files already as well.

Performance is not always predictable. While many systems will be faster with parallel file reads some will prefer sequential. Some are dynamic and may shift under load, optimisations that offer latency do not always yield good throughput under heavy contention.

There is also no error handling in that example. If something requires them to either all be successfully shown or not at all it won't do that.

In depth experimentation is recommended with console.log at each stage and fake file read solutions (random delay instead). Although many solutions appear to do the same in simple cases all have subtle differences that take some extra scrutiny to squeeze out.

Use this mock to help tell the difference between solutions:

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

£噩梦荏苒 2025-01-24 13:54:19

使用 Task、futurize 和可遍历列表,您可以简单地执行

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

Using Task, futurize, and a traversable List, you can simply do

async function printFiles() {
  const files = await getFiles();

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

Here is how you'd set this up

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)

Another way to have structured the desired code would be

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

Or perhaps even more functionally oriented

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

Then from the parent function

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

If you really wanted more flexibility in encoding, you could just do this (for fun, I'm using the proposed Pipe Forward operator )

import { curry, flip } from 'ramda'

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

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

PS - I didn't try this code on the console, might have some typos... "straight freestyle, off the top of the dome!" as the 90s kids would say. :-p

仅此而已 2025-01-24 13:54:19

正如其他答案所提到的,您可能希望它按顺序而不是并行执行。 IE。运行第一个文件,等待它完成,然后完成后运行第二个文件。那不会发生什么。

我认为解决为什么这种情况没有发生很重要。

想想 forEach 是如何工作的。我找不到源代码,但我认为它的工作原理是这样的:

const forEach = (arr, cb) => {
  for (let i = 0; i < arr.length; i++) {
    cb(arr[i]);
  }
};

现在想想当你执行这样的操作时会发生什么:

forEach(files, async logFile(file) {
  const contents = await fs.readFile(file, 'utf8');
  console.log(contents);
});

forEachfor 循环中,我们正在调用 cb(arr[i]),最终成为 logFile(file)logFile 函数内部有一个 await,因此 for 循环可能会在继续之前等待这个 await i++

不,不会的。令人困惑的是,这并不是 await 的工作原理。来自文档

await 分割执行流程,允许异步函数的调用者恢复执行。在等待推迟异步函数的继续之后,随后执行后续语句。如果此等待是其函数执行的最后一个表达式,则通过向函数的调用者返回一个待完成的 Promise 来继续执行等待的函数并恢复该调用者的执行。

因此,如果您有以下内容,则不会在 "b" 之前记录数字:

const delay = (ms) => {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
};

const logNumbers = async () => {
  console.log(1);
  await delay(2000);
  console.log(2);
  await delay(2000);
  console.log(3);
};

const main = () => {
  console.log("a");
  logNumbers();
  console.log("b");
};

main();

循环回到 forEachforEach 就像 >mainlogFile 就像 logNumbers 一样。 main 不会仅仅因为 logNumbers 做了一些 await 操作而停止,并且 forEach 不会仅仅因为 logNumbers 停止 < code>logFile 做了一些await操作。

As other answers have mentioned, you're probably wanting it to be executed in sequence rather in parallel. Ie. run for first file, wait until it's done, then once it's done run for second file. That's not what will happen.

I think it's important to address why this doesn't happen.

Think about how forEach works. I can't find the source, but I presume it works something like this:

const forEach = (arr, cb) => {
  for (let i = 0; i < arr.length; i++) {
    cb(arr[i]);
  }
};

Now think about what happens when you do something like this:

forEach(files, async logFile(file) {
  const contents = await fs.readFile(file, 'utf8');
  console.log(contents);
});

Inside forEach's for loop we're calling cb(arr[i]), which ends up being logFile(file). The logFile function has an await inside it, so maybe the for loop will wait for this await before proceeding to i++?

No, it won't. Confusingly, that's not how await works. From the docs:

An await splits execution flow, allowing the caller of the async function to resume execution. After the await defers the continuation of the async function, execution of subsequent statements ensues. If this await is the last expression executed by its function execution continues by returning to the function's caller a pending Promise for completion of the await's function and resuming execution of that caller.

So if you have the following, the numbers won't be logged before "b":

const delay = (ms) => {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
};

const logNumbers = async () => {
  console.log(1);
  await delay(2000);
  console.log(2);
  await delay(2000);
  console.log(3);
};

const main = () => {
  console.log("a");
  logNumbers();
  console.log("b");
};

main();

Circling back to forEach, forEach is like main and logFile is like logNumbers. main won't stop just because logNumbers does some awaiting, and forEach won't stop just because logFile does some awaiting.

风启觞 2025-01-24 13:54:19

类似于Antonio Val的 p-titeration ,替代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();

or async-af 具有静态方法(log/logaf),可log :

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

Similar to Antonio Val's p-iteration, an alternative npm module is 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();

Alternatively, async-af has a static method (log/logAF) that logs the results of 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();

However, the main advantage of the library is that you can chain asynchronous methods to do something like:

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

长梦不多时 2025-01-24 13:54:19

这是在 forEach 循环中使用异步的一个很好的例子。

编写自己的asyncForEach

async function asyncForEach(array, callback) {  
    for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array)
    }
}

你可以像这样使用它

await asyncForEach(array, async function(item,index,array){
     //await here
   }
)

Here is a great example for using async in forEach loop.

Write your own asyncForEach

async function asyncForEach(array, callback) {  
    for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array)
    }
}

You can use it like this

await asyncForEach(array, async function(item,index,array){
     //await here
   }
)
听,心雨的声音 2025-01-24 13:54:19

如果您想同时迭代所有元素:

async function asyncForEach(arr, fn) {
  await Promise.all(arr.map(fn));
}

如果您想非同时迭代所有元素(例如,当您的映射函数有副作用或一次在所有数组元素上运行映射器将过于耗费资源):

选项 A:Promise

function asyncForEachStrict(arr, fn) {
  return new Promise((resolve) => {
    arr.reduce(
      (promise, cur, idx) => promise
        .then(() => fn(cur, idx, arr)),
      Promise.resolve(),
    ).then(() => resolve());
  });
}

选项 B:异步/等待

async function asyncForEachStrict(arr, fn) {
  for (let idx = 0; idx < arr.length; idx += 1) {
    const cur = arr[idx];

    await fn(cur, idx, arr);
  }
}

If you'd like to iterate over all elements concurrently:

async function asyncForEach(arr, fn) {
  await Promise.all(arr.map(fn));
}

If you'd like to iterate over all elements non-concurrently (e.g. when your mapping function has side effects or running mapper over all array elements at once would be too resource costly):

Option A: Promises

function asyncForEachStrict(arr, fn) {
  return new Promise((resolve) => {
    arr.reduce(
      (promise, cur, idx) => promise
        .then(() => fn(cur, idx, arr)),
      Promise.resolve(),
    ).then(() => resolve());
  });
}

Option B: async/await

async function asyncForEachStrict(arr, fn) {
  for (let idx = 0; idx < arr.length; idx += 1) {
    const cur = arr[idx];

    await fn(cur, idx, arr);
  }
}
忘东忘西忘不掉你 2025-01-24 13:54:19

的包装器

  • 对于打字稿用户,a Promise.all(array.map(iterator))使用 Promise.all(array.map(iterator))具有正确类型 ,因为打字稿的STDLIB支持已经处理通用。
  • 但是,复制粘贴 promise.all(array.map(iterator))每次需要async映射显然是次优的, promise.all(array.map(iterator))不能很好地传达代码的意图 - 因此,大多数开发人员都会将其包装到 asyncmap()包装器函数中。但是,这样做需要使用仿制药来确保以 start value设置的值=等待asyncmap()具有正确的类型。
export const asyncMap = async <ArrayItemType, IteratorReturnType>(
  array: Array<ArrayItemType>,
  iterator: (
    value: ArrayItemType,
    index?: number
  ) => Promise<IteratorReturnType>
): Promise<Array<IteratorReturnType>> => {
  return Promise.all(array.map(iterator));
};

和快速测试:

it(`runs 3 items in parallel and returns results`, async () => {
  const result = await asyncMap([1, 2, 3], async (item: number) => {
    await sleep(item * 100);
    return `Finished ${item}`;
  });
  expect(result.length).toEqual(3);
  // Each item takes 100, 200 and 300ms
  // So restricting this test to 300ms plus some leeway
}, 320);

sleep()只是:

const sleep = async (timeInMs: number): Promise<void> => {
  return new Promise((resolve) => setTimeout(resolve, timeInMs));
};

For TypeScript users, a Promise.all(array.map(iterator)) wrapper with working types

  • Using Promise.all(array.map(iterator)) has correct types since the TypeScript's stdlib support already handles generics.
  • However copy pasting Promise.all(array.map(iterator)) every time you need an async map is obviously suboptimal, and Promise.all(array.map(iterator)) doesn't convey the intention of the code very well - so most developers would wrap this into an asyncMap() wrapper function. However doing this requires use of generics to ensure that values set with const value = await asyncMap() have the correct type.
export const asyncMap = async <ArrayItemType, IteratorReturnType>(
  array: Array<ArrayItemType>,
  iterator: (
    value: ArrayItemType,
    index?: number
  ) => Promise<IteratorReturnType>
): Promise<Array<IteratorReturnType>> => {
  return Promise.all(array.map(iterator));
};

And a quick test:

it(`runs 3 items in parallel and returns results`, async () => {
  const result = await asyncMap([1, 2, 3], async (item: number) => {
    await sleep(item * 100);
    return `Finished ${item}`;
  });
  expect(result.length).toEqual(3);
  // Each item takes 100, 200 and 300ms
  // So restricting this test to 300ms plus some leeway
}, 320);

sleep() is just:

const sleep = async (timeInMs: number): Promise<void> => {
  return new Promise((resolve) => setTimeout(resolve, timeInMs));
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文