Promise.then在之前的.then和foreach之前完成

时间:2020-04-04 21:00:29

标签: javascript asynchronous foreach promise

我正在运行一个半复杂的Promise链,中间有一个foreach循环。我遇到的问题是,最后的.then()在foreach循环完成之前就被命中,从而导致dailyTotals数组完全为空。

fs.readdir(csvPath)
    .then(files => {
        // Define storage array
        var csvFiles = [];

        // Loop through and remove non-csv
        files.forEach(file => {
            if (file !== "README.md" && file !== ".gitignore") {
                csvFiles.push(file);
            }
        });

        return csvFiles;
    })
    .then(files => {
        var dailyTotals = [];

        files.forEach(filename => {
            const loadedFile = fs.createReadStream(csvPath + filename);

            var totalCases = 0;
            var totalDeaths = 0;
            var totalRecovered = 0;

            papa.parse(loadedFile, {
                header: true,
                worker: true,
                step: r => {
                    totalCases += parseIntegerValue(r.data.Confirmed);
                    totalDeaths += parseIntegerValue(r.data.Deaths);
                    totalRecovered += parseIntegerValue(r.data.Recovered);
                },
                complete: () => {
                    var dailyTotal = {
                        date: filename.replace(".csv", ""),
                        parsed: {
                            confirmed: totalCases,
                            deaths: totalDeaths,
                            recovered: totalRecovered
                        }
                    };

                    dailyTotals.push(dailyTotal);
                }
            });
        });

        return dailyTotals;
    })
    .then(dailyTotals => {
        console.log(dailyTotals);
    });

在解决下一个.then()之前,有没有办法等待该foreach循环完成?问题直接在foreach和最终console.log(dailyTotals);

3 个答案:

答案 0 :(得分:2)

不。您将无法保证等到.forEach之后再返回。如果您的.forEach函数包含异步代码,则不应使用该代码。 相反,您可以采用略有不同的方法:

  1. 使用传统的for循环,因为它会阻塞。
  2. 创建范围承诺.map,类似于您对。forEach所做的操作,我建议这样做:
const promises = [array with your values].map(() => {
   // If you are running in a node env, you can also make the function async instead of returning a promise
   return new Promise((resolve, reject) => {
       // Do what you need to
       if (error) {
          reject(error);
       }

       resolve(<insert the value that should be returned here>);

   });
});

Promise.all(promises).then(() => console.log('all promises done'));

使用一些简单的console.log来查看其工作示例。日志:https://jsfiddle.net/3ghmsaqj/

答案 1 :(得分:2)

首先,我不得不提到Array#forEach之外还有其他方法:

  • Array#map,其目的是通过映射原始数组(与对.forEach的操作相同)来创建新数组,并且
  • Array#filter(它的名字不言而喻)。

问题是,您有一个Promise链和一个基于回调的模块。要混合使用它们,请使用Promise构造函数并使用Promise.all来将它们组合为一个promise:

fs.readdir(csvPath)
    .then(files => {

        // Filter out non-csv
        return files.filter(file => (file !== "README.md" && file !== ".gitignore"))
    })
    .then(files => {
        //Returning a Promise from then handler will be awaited
        return Promise.all(files.map(filename => {
            const loadedFile = fs.createReadStream(csvPath + filename);
            return new Promise(resolve => {
                var totalCases = 0;
                var totalDeaths = 0;
                var totalRecovered = 0;

                papa.parse(loadedFile, {
                    header: true,
                    worker: true,
                    step: r => {
                        totalCases += parseIntegerValue(r.data.Confirmed);
                        totalDeaths += parseIntegerValue(r.data.Deaths);
                        totalRecovered += parseIntegerValue(r.data.Recovered);
                    },
                    complete: () => {
                        resolve( {
                          date: filename.replace(".csv", ""),
                          parsed: {
                            confirmed: totalCases,
                            deaths: totalDeaths,
                            recovered: totalRecovered
                          }
                        });
                    }
                });
            });
        }));
    })
    .then(dailyTotals => {
        console.log(dailyTotals);
    });

您甚至可以省略第一个.then处理程序,因为.filter是同步操作,并且可以在单个.then内完成所有操作:

fs.readdir(csvPath)
    .then(files => {
        //Returning a Promise from then handler will be awaited
        return Promise.all(
            files
                //Filter here
                .filter(file => (file !== "README.md" && file !== ".gitignore"))
                //And map without a second then
                .map(filename => {
                    const loadedFile = fs.createReadStream(csvPath + filename);
                    return new Promise(resolve => {
                        var totalCases = 0;
                        var totalDeaths = 0;
                        var totalRecovered = 0;

                        papa.parse(loadedFile, {
                            header: true,
                            worker: true,
                            step: r => {
                                totalCases += parseIntegerValue(r.data.Confirmed);
                                totalDeaths += parseIntegerValue(r.data.Deaths);
                                totalRecovered += parseIntegerValue(r.data.Recovered);
                            },
                            complete: () => {
                                resolve( {
                                  date: filename.replace(".csv", ""),
                                  parsed: {
                                    confirmed: totalCases,
                                    deaths: totalDeaths,
                                    recovered: totalRecovered
                                  }
                                });
                            }
                        });
                    });
                })
        );
    })
    .then(dailyTotals => {
        console.log(dailyTotals);
    });

答案 2 :(得分:0)

您的第二个display-style具有异步代码then,因此您的第二个papa.parse()中的每个代码将与空值同步完成,因此最后一个then将使用这些空值执行。您只需在异步代码完成后从第二个then块返回

您将不得不在then回调中解析promise,并使用complete之类来检测何时执行所有Promise.all回调。

1)将每个地图更改为一张地图。

complete

2)从地图回调中返回一个承诺。

const arrayPromises= files.map(... Etc)

3)从files.map((file) => new Promise((resolve, reject) => {... Everything from your current foraeach}))回调中兑现您的诺言

complete

4)使用reolve()

相关问题