【问题标题】:Promise.then being resolved before previous .then with foreach completesPromise.then 在前一个 .then 之前被解决,foreach 完成
【发布时间】:2020-04-04 21:00:29
【问题描述】:

我正在运行一个半复杂的承诺链,中间有一个 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);

【问题讨论】:

  • 查看Promise.all
  • 没有。你不会让.forEach 等待。但是,您还可以通过稍微不同的方法来实现这一目标: 1. 使用传统的 for 循环。
  • Promise.allSettled

标签: javascript asynchronous foreach promise


【解决方案1】:

首先不得不提一下Array#forEach还有替代方法:

  • Array#map 旨在通过映射原始数组来创建一个新数组(与 .forEach 所做的相同),以及
  • Array#filter(它的名字不言自明)。

问题是,你有一个承诺链和一个基于回调的模块。要混合它们,请使用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】:

    没有。在返回之前,您不会得到等待.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/

    【讨论】:

    • 我没有关注...这对我来说很难理解。
    • 我更新了我的示例。你能再检查一下,看看它是否更有意义?
    【解决方案3】:

    看起来您的第二个 then 具有异步代码 papa.parse(),因此您在第二个 then 中的 for each 将使用空值同步完成,因此最终的 then 将使用这些空值执行。当您的异步代码完成时,您只需要从第二个 then 块返回

    您必须在complete 回调中解析承诺,并使用Promise.all 之类的方法来检测所有complete 回调何时执行

    1) 将您的 for each 更改为地图..

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

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

    files.map((file) =&gt; new Promise((resolve, reject) =&gt; {... Everything from your current foraeach}))

    3) 在complete 回调中循环你的承诺

    reolve()

    4) 使用return Promise.all(arrayPromises)

    【讨论】:

    • 你能用代码解释一下吗?仅凭文字很难理解。
    • 我正在使用手机,很难编辑您的尝试。查看 Promise.all 文档。您需要创建一个代表您的 for 循环的 Promise 数组,并在 complete 回调中解析每个 Promise。您将在第二个thenreturn Promise.all(arrayPromises)。基本上你的 for 循环包含异步代码。您只想在所有异步操作完成后返回。在你的第二个then 在异步代码完成之前返回的那一刻
    猜你喜欢
    • 2016-05-13
    • 2018-04-28
    • 2018-05-22
    • 2019-03-07
    • 1970-01-01
    • 2020-12-27
    • 1970-01-01
    • 2019-02-22
    • 2017-12-31
    相关资源
    最近更新 更多