Javascript 等待 .forEach() 完成的最佳方式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38406920/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 21:27:26  来源:igfitidea点击:

Best way to wait for .forEach() to complete

javascriptangularjs

提问by Jaap Weijland

Sometimes I need to wait for a .forEach()method to finish, mostly on 'loader' functions. This is the way I do that:

有时我需要等待一个.forEach()方法完成,主要是在 'loader' 函数上。这是我这样做的方式:

$q.when(array.forEach(function(item){ 
    //iterate on something 
})).then(function(){ 
    //continue with processing 
});

I can't help but feel that this isn't the best way to wait for a .forEach()to finish. What is the best way to do this?

我不禁觉得这不是等待.forEach()完成的最佳方式。做这个的最好方式是什么?

采纳答案by Ismail RBOUH

If there is no asynchronouscode inside the forEach, forEachis not asynchronous, for example in this code:

如果里面没有异步代码forEachforEach则不是异步的,例如在这段代码中:

array.forEach(function(item){ 
    //iterate on something 
});
alert("Foreach DONE !");

you will see the alert after forEachfinished.

forEach完成后您将看到警报。

Otherwise (You have something asynchronous inside), you can wrap the forEachloop in a Promise:

否则(您内部有一些异步内容),您可以将forEach循环包装在Promise 中

var bar = new Promise((resolve, reject) => {
    foo.forEach((value, index, array) => {
        console.log(value);
        if (index === array.length -1) resolve();
    });
});

bar.then(() => {
    console.log('All done!');
});

Credit: @rolando-benjamin-vaz-ferreira

图片来源:@rolando-benjamin-vaz-ferreira

回答by Rolando Benjamin Vaz Ferreira

var foo = [1,2,3,4,5,6,7,8,9,10];

If you're actually doing async stuff inside the loop, you can wrap it in a promise ...

如果您实际上是在循环中执行异步操作,则可以将其包装在一个 promise 中...

var bar = new Promise((resolve, reject) => {
    foo.forEach((value, index, array) => {
        console.log(value);
        if (index === array.length -1) resolve();
    });
});

bar.then(() => {
    console.log('All done!');
});

回答by Douglas Rosebank

The quickest way to make this work using ES6 would be just to use a for..ofloop.

使用 ES6 完成这项工作的最快方法就是使用for..of循环。

const myAsyncLoopFunction = async (array) {
  const allAsyncResults = []

  for (const item of array) {
    const asnycResult = await asyncFunction(item)
    allAsyncResults.push(asyncResult)
  }

  return allAsyncResults
}

Or you could loop over all these async requests in parallel using Promise.all()like this:

或者您可以使用Promise.all()如下方式并行循环所有这些异步请求:

const myAsyncLoopFunction = async (array) {
  const promises = array.map(asyncFunction)
  await Promise.all(promises)
  console.log(`All async tasks complete!`)
}

回答by u9953894

forEach() doesn't return anything, so a better practice would be map()+ Promise.all()

forEach() 不返回任何内容,因此更好的做法是map()+Promise.all()

var arr = [1, 2, 3, 4, 5, 6]

var doublify = (ele) => {
  return new Promise((res, rej) => {
    setTimeout(() => {
        res(ele * 2)
    }, Math.random() ); // Math.random returns a random number from 0~1
  })
}

var promises = arr.map(async (ele) => {
  // do some operation on ele
  // ex: var result = await some_async_function_that_return_a_promise(ele)
  // In the below I use doublify() to be such an async function

  var result = await doublify(ele)
  return new Promise((res, rej) => {res(result)})
})

Promise.all(promises)
.then((results) => {
  // do what you want on the results
  console.log(results)
})

output

输出

回答by About7Deaths

I'm not sure of the efficiency of this version compared to others, but I used this recently when I had an asynchronous function inside of my forEach(). It does not use promises, mapping, or for-of loops:

我不确定这个版本与其他版本相比的效率,但我最近在 forEach() 中有一个异步函数时使用了这个。它不使用承诺、映射或 for-of 循​​环:

// n'th triangular number recursion (aka factorial addition)
function triangularNumber(n) {
    if (n <= 1) {
        return n
    } else {
        return n + triangularNumber(n-1)
    }
}

// Example function that waits for each forEach() iteraction to complete
function testFunction() {
    // Example array with values 0 to USER_INPUT
    var USER_INPUT = 100;
    var EXAMPLE_ARRAY = Array.apply(null, {length: USER_INPUT}).map(Number.call, Number) // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, n_final... ] where n_final = USER_INPUT-1

    // Actual function used with whatever actual array you have
    var arrayLength = EXAMPLE_ARRAY.length
    var countMax = triangularNumber(arrayLength);
    var counter = 0;
    EXAMPLE_ARRAY.forEach(function(entry, index) {
        console.log(index+1); // show index for example (which can sometimes return asynchrounous results)

        counter += 1;
        if (triangularNumber(counter) == countMax) {

            // function called after forEach() is complete here
            completionFunction();
        } else {
            // example just to print counting values when max not reached
            // else would typically be excluded
            console.log("Counter index: "+counter);
            console.log("Count value: "+triangularNumber(counter));
            console.log("Count max: "+countMax);
        }
    });
}
testFunction();

function completionFunction() {
    console.log("COUNT MAX REACHED");
}

回答by Federico Caccia

I had to deal with the same problem (forEachusing multiple promises inside) and none of the solutions presented at the current date were helpful for me. So I implemented a check array, were each promise updates its complete status. We have a general promise that wraps the process. We only resolve the general promise when each promise completed. Snippet code:

我不得不处理同样的问题(forEach在内部使用多个 promises)并且当前日期提供的解决方案都没有对我有帮助。所以我实现了一个检查数组,每个承诺更新其完整状态。我们有一个总体承诺来包装这个过程。我们只在每个承诺完成时解决一般承诺。片段代码:

function WaitForEachToResolve(fields){

    var checked_fields = new Array(fields.length).fill(0);
    const reducer = (accumulator, currentValue) => accumulator + currentValue;

    return new Promise((resolve, reject) => {

      Object.keys(fields).forEach((key, index, array) => {

        SomeAsyncFunc(key)
        .then((result) => {

            // some result post process

            checked_fields[index] = 1;
            if (checked_fields.reduce(reducer) === checked_fields.length)
                resolve();
        })
        .catch((err) => {
            reject(err);
        });
      }
    )}
}

回答by itsHarshad

I like to use async-awaitinstead of .then() syntax so for asynchronous processing of data, modified the answer of @Ronaldo this way -

我喜欢使用async-await而不是 .then() 语法,因此对于数据的异步处理,以这种方式修改了@Ronaldo 的答案 -

let finalData = [];
var bar = new Promise(resolve => {
    foo.forEach((value, index) => {
        const dataToGet = await abcService.getXyzData(value);
        finalData[index].someKey = dataToGet.thatOtherKey;
        // any other processing here
        if (finalData[dataToGet.length - 1].someKey) resolve();
    });
});

await Promise.all([bar]);
console.log(`finalData: ${finalData}`);

NOTE: I've modified the if condition where it resolves the promise to meet my conditions. You can do the same in your case.

注意我已经修改了 if 条件,它解决了满足我的条件的承诺。你可以在你的情况下做同样的事情。

回答by Terris

Alter and check a counter at the end of every possible unique branch of code, including callbacks. Example:

在每个可能的唯一代码分支(包括回调)末尾更改并检查计数器。例子:

const fs = require('fs');

/**
 * @description Delete files older than 1 day
 * @param {String} directory - The directory to purge
 * @return {Promise}
 */
async function purgeFiles(directory) {
  const maxAge = 24*3600000;
  const now = Date.now();
  const cutoff = now-maxAge;

  let filesPurged = 0;
  let filesProcessed = 0;
  let purgedSize = 0;

  await new Promise( (resolve, reject) => {
    fs.readdir(directory, (err, files) => {
      if (err) {
        return reject(err);
      }
      if (!files.length) {
        return resolve();
      }
      files.forEach( file => {
        const path = `${directory}/${file}`;
        fs.stat(path, (err, stats)=> {
          if (err) {
            console.log(err);
            if (++filesProcessed === files.length) resolve();
          }
          else if (stats.isFile() && stats.birthtimeMs < cutoff) {
            const ageSeconds = parseInt((now-stats.birthtimeMs)/1000);
            fs.unlink(path, error => {
              if (error) {
                console.log(`Deleting file failed: ${path} ${error}`);
              }
              else {
                ++filesPurged;
                purgedSize += stats.size;
                console.log(`Deleted file with age ${ageSeconds} seconds: ${path}`);
              }
              if (++filesProcessed === files.length) resolve();
            });
          }
          else if (++filesProcessed === files.length) resolve();
        });
      });
    });
  });

  console.log(JSON.stringify({
    directory,
    filesProcessed,
    filesPurged,
    purgedSize,
  }));
}

// !!DANGER!! Change this line! (intentional syntax error in ,')
const directory = ,'/tmp'; // !!DANGER!! Changeme
purgeFiles(directory).catch(error=>console.log(error));

回答by Wei Tang

A universal solution for making sure that all forEach() elements finished execution.

确保所有 forEach() 元素完成执行的通用解决方案。

const testArray = [1,2,3,4]
let count = 0

await new Promise( (resolve) => {
  testArray.forEach( (num) => {
    try {
      //some real logic
      num = num * 2
    } catch (e) {
      // error handling
      console.log(e)
    } fanally {
      // most important is here
      count += 1
      if (count == testArray.length) {
        resolve()
      }
    }
  })  
})

The idea is same with the answer using index to count. But in real case, if error happened, the index way cannot count correctly. So the solution is more robust.

这个想法与使用索引计数的答案相同。但在实际情况下,如果发生错误,索引方式将无法正确计数。因此该解决方案更加稳健。

Thx

谢谢