在循环内对模型创建的嵌套承诺进行续集

时间:2018-11-07 17:23:44

标签: node.js promise sequelize.js

我有一个异步函数(createObjects),该函数需要在数据库中创建一些模型,因此,在创建所有对象之前(在forEach循环内),该函数需要等待。创建最后一个模型后,它应该返回“数据已同步!”。字符串,但“数据已同步!”消息永远不会等待createObjects()完成。我认为我需要将所有model.create promises都返回给mainPromise,就像一个promises数组一样,但是我不知道这样做的最好方法。有什么建议吗?

PS:在createObjects内部调用的calculateCost是异步的,并且运行良好。

mainPromise()
.then( (data) => {
    return proccessData(data); //this is a sync function
})
.then( (newData) => {
    createObjects(newData); // this is a async function
})
.then( () => {
    return "Data Synchronized!";
})

//this needs to be an async function
function createObjects(newData){
newData.forEach((bills) => {
    //if the object bills has the Groups array attributes...
    if (bills.Groups.length !== 0) {
        //iterate through groups
        bills.Groups.forEach( (group) => {
        var uid = group.id;
        var usage = group.Metric.UsageAmount;
        var cost = calculateCost(usage, uid); //async function

        //the problem is here
        cost.then((result) => {
            models.Billing.create({
                group_id: uid,
                cost: result,
                usage: usage          
            });
        });
        })
    }
});
}

var calculateCost = (usage, uid) => {
    var cost;

    return models.ObjectA.findOne({
        where: { id: uid }
    }).then((data) => {
        if (data.type == "Interactive") {
            cost = usage * 0.44;
        } else if (data.type == "Automated") {
            cost = usage * 0.11;
        }
        return cost;
    });
}

1 个答案:

答案 0 :(得分:1)

您的代码中没有任何内容可以监视cost().then(...)的结果,因此一点点代码都是一劳永逸的。调用models.Billing.create和代码顶部的then之一也是如此。这就是为什么您看到自己的结果。使用Promises时,请留意要创建诺言的地方,而不要将它们退还给更高的呼叫者。这通常表明创建了未被遵守的承诺。

要解决此问题:

首先,将then固定在代码顶部,以便实际上返回createObjects的结果:

.then( (newData) => {
    return createObjects(newData); // this is a async function
})

更好:

.then(createObjects)

在纠正之后...

选项1-使用reduce代替forEach

如果要确保一次(依次)执行查询,而不是一次执行所有查询,请使用这种方法:

function processBillGroups(groups) {
    return groups.reduce((last, group) => {
        var group_id = group.id;
        var usage = group.Metric.UsageAmount;

        return last
            .then(() => calculateCost(usage, group_id))
            .then((cost) => models.Billing.create({ group_id, cost, usage }))
    }, Promise.resolve());
}

function createObjects(newData) {
    return newData.reduce(
        (last, { Groups }) => last.then(() => processBillGroups(Groups)),
        Promise.resolve(),
    );
}

选项1.1使用async / await

这也将按顺序执行操作,但是使用async / await语法而不是直接的Promise操作。

async function processBillGroups(groups) {
    for (group of groups) {
        let group_id = group.id;
        let usage = group.Metric.UsageAmount;

        let cost = await calculateCost(usage, group_id);

        await models.Billing.create({ group_id, cost, usage });
    }
}

async function createObjects(newData) {
    for ({ Groups } of newData) {
        await processBillGroups(Groups);
    }
}

选项2-使用mapPromise.all代替forEach

如果您不介意同时(并行)执行所有动作,甚至不希望它们并行执行,请使用此选项。 createObjects将返回一个诺言,当所有动作完成时,诺言将解决:

function processBillGroups(groups) {
    return Promise.all(groups.map((group) => {
        var group_id = group.id;
        var usage = group.Metric.UsageAmount;

        return calculateCost(usage, group_id)
            .then((cost) => models.Billing.create({ group_id, cost, usage }));
    }));
}

function createObjects(newData) {
    return Promise.all(
        newData.map(({ Groups }) => processBillGroups(Groups))
    );
}

选项2.1-使用map / Promise.allasyncawait

行为与选项2相似,但语法更好一些。

function processBillGroups(groups) {
    return Promise.all(groups.map(async (group) => {
        let group_id = group.id;
        let usage = group.Metric.UsageAmount;

        let cost = await calculateCost(usage, group_id);

        await models.Billing.create({ group_id, cost, usage });
    }));
}

function createObjects(newData) {
    return Promise.all(
        newData.map(({ Groups }) => processBillGroups(Groups))
    );
}