为什么在一个循环中我的诺言存在时间问题,我该如何解决?

时间:2018-09-11 13:19:03

标签: javascript node.js promise

在我的爱好节点项目中,我对此问题一无所知。我有一个函数(processDataSet)正在处理数据数组(inputArray)并返回一个Promise。该函数使用for循环遍历输入数组,并在每一轮中调用saveObjectData函数。此保存功能处理单个数据输入,并返回承诺。

如果saveObjectData函数失败,则processDataSet函数会捕获返回的拒绝,但似乎自己的reject在for循环中未正确调用。我认为这是一个计时问题,我不理解。在代码下方查看输出打印结果。

function processDataSet(inputArray, scriptConfig) {
    var contentType = scriptConfig.contentType;
    return new Promise(function(resolve, reject) {
        if(!Array.isArray(inputArray)) {
            return reject(new Error("Input data is not an array. Cannot process."));
        }
        if(!scriptConfig) {
            return reject(new Error("Invalid scriptConfig"));
        }
        if(!typeof contentType === "string" && !contentType instanceof String) {
            return reject(new Error("Invalid contentType for the data set. The parameter should be a string."));
        }

        console.log("Post processing data for the script " + scriptConfig.name + " (type: " + scriptConfig.contentType + ")");

        // Iterate through the input array and handle the objects one-by-one
        for (var i = 0; i < inputArray.length; i++) {
            saveObjectData(inputArray[i], scriptConfig)
            .then(() => {
                //continue;
            })
            .catch(err => {
                console.log("TEST PRINT " + scriptConfig.name);
                return reject(new Error("Processing data object failed.", err));
            });
        }
        console.log("Resolve " + scriptConfig.name);
        return resolve();
    });
}

在控制台中输出打印:

Post processing data for the script Script1 (type: Season)
Resolve Script1
TEST PRINT Script1

似乎在错误处理程序的“ TEST PRINT ...”之前打印了包括“ Resolve ...”的最后一个日志行。为什么会这样?如何使执行从processDataSet返回之前等待所有数据条目的完整解析?

我不确定让processDataSet返回承诺是否多余,但是我将其作为故障排除的一部分。

3 个答案:

答案 0 :(得分:5)

您的for循环不会一一保存对象。它开始保存第一个,然后保存第二个,依此类推,然后循环结束,您可以立即解决您的诺言。只有在循环中创建的承诺之后,这些承诺才会成立,并且其中一些可能会拒绝接受已经兑现的承诺。

避免使用Promise constructor antipattern,而是正确地兑现您的诺言。

如果可以立即触发所有保存以使其同时运行,则可以使用Promise.all等待所有的承诺:

function processDataSet(inputArray, scriptConfig) {
    if (!Array.isArray(inputArray)) {
        return Promise.reject(new Error("Input data is not an array. Cannot process."));
    }
    if (!scriptConfig) {
        return Promise.reject(new Error("Invalid scriptConfig"));
    }
    var contentType = scriptConfig.contentType;
    if (typeof contentType !== "string") {
        return Promise.reject(new Error("Invalid contentType for the data set. The parameter should be a string."));
    }

    console.log("Post processing data for the script " + scriptConfig.name + " (type: " + scriptConfig.contentType + ")");

    return Promise.all(inputArray.map(input => {
        return saveObjectData(input, scriptConfig)
        .catch(err => {
            console.log("TEST PRINT " + scriptConfig.name);
            throw new Error("Processing data object failed.", input, err);
        });
    })).then(results => {
        console.log("Resolve " + scriptConfig.name, results);
        return;
    });
}

如果您坚持按顺序保存它们,建议使用async / await

async function processDataSet(inputArray, scriptConfig) {
    if (!Array.isArray(inputArray)) {
        throw new Error("Input data is not an array. Cannot process.");
    }
    if (!scriptConfig) {
        throw new Error("Invalid scriptConfig");
    }
    var contentType = scriptConfig.contentType;
    if (typeof contentType !== "string") {
        throw new Error("Invalid contentType for the data set. The parameter should be a string.");
    }

    console.log("Post processing data for the script " + scriptConfig.name + " (type: " + scriptConfig.contentType + ")");

    for (var input of inputArray) {
        try {
             await saveObjectData(input, scriptConfig);
        } catch (err) {
            console.log("TEST PRINT " + scriptConfig.name);
            throw new Error("Processing data object failed.", input, err);
        }
    }
    console.log("Resolve " + scriptConfig.name);
}

答案 1 :(得分:2)

您的循环是异步的,并立即返回。

    for (var i = 0; i < inputArray.length; i++) {
        saveObjectData(inputArray[i], scriptConfig)
        .then(() => {
            //continue;
        })
        .catch(err => {
            console.log("TEST PRINT " + scriptConfig.name);
            return reject(new Error("Processing data object failed.", err));
        });
    }

您需要从每个迭代中获得一个承诺,等待所有的承诺,然后调用

console.log("Resolve " + scriptConfig.name);
        return resolve();

类似这样的东西:

const promises = []
// Iterate through the input array and handle the objects one-by-one
for (var i = 0; i < inputArray.length; i++) {
  promises.push(saveObjectData(inputArray[i], scriptConfig))
}

Promise.all(promises).then(results => {
  resolve();
})

您经常会看到在这种情况下使用的地图功能...

const promises = inputArray.map(it => saveObjectData(it, scriptConfig))

答案 2 :(得分:2)

saveObjectData是异步的,您不必等待它完成。您必须等待它在.then.catch中完成。也就是说,您的console.log('Resolve ' ...需要在.then(或.catch)中完成。当然,由于您可以通过循环实现一系列承诺,因此您要等待所有这些承诺完成。您可以等待一系列的Promise.all承诺。

function processDataSet(inputArray, scriptConfig) {
    const contentType = scriptConfig.contentType;
    return new Promise(function(resolve, reject) {
        if(!Array.isArray(inputArray)) {
            return reject(new Error("Input data is not an array. Cannot process."));
        }
        if(!scriptConfig) {
            return reject(new Error("Invalid scriptConfig"));
        }
        if(!typeof contentType === "string" && !contentType instanceof String) {
            return reject(new Error("Invalid contentType for the data set. The parameter should be a string."));
        }
        resolve();
    }).then(() => {
        console.log("Post processing data for the script " + scriptConfig.name + " (type: " + scriptConfig.contentType + ")");

        return Promise.all(inputArray.map(input =>
            saveObjectData(input, scriptConfig)
            .then(() => {
                //continue;
            })
            .catch(err => {
                console.log("TEST PRINT " + scriptConfig.name);
                return reject(new Error("Processing data object failed.", err));
            })
        ));
    }).then(() => console.log(`Resolve ${scripConfig.name}`));
}

我认为使用async / await可以使这一点更容易理解。

async function processDataSet(inputArray, scriptConfig) {
    const contentType = scriptConfig.contentType;
    if (!Array.isArray(inputArray)) {
        throw new Error("Input data is not an array. Cannot process.");
    }
    if (!scriptConfig) {
        throw new Error("Invalid scriptConfig");
    }
    if (!typeof contentType === "string" && !contentType instanceof String) {
        throw new Error("Invalid contentType for the data set. The parameter should be a string.");
    }

    console.log("Post processing data for the script " + scriptConfig.name + " (type: " + scriptConfig.contentType + ")");

    await Promise.all(inputArray.map(async input => {
        try {
            saveObjectData(input, scriptConfig);
            // continue
        } catch (err) {
            console.log("TEST PRINT " + scriptConfig.name);
            throw new Error("Processing data object failed.", err);
        }
    }));
    console.log(`Resolve ${scriptConfig.name}`);
}