Sequelize вложенное обещание на модели создать внутри цикла - PullRequest
0 голосов
/ 07 ноября 2018

У меня есть асинхронная функция (createObjects), которая должна создавать некоторые модели в базе данных, поэтому, пока все объекты не созданы (внутри цикла forEach), функция должна ждать. После создания последней модели она должна вернуть «Синхронизированные данные!» строка, но "Данные синхронизированы!" сообщение никогда не ожидает завершения createObjects (). Я думаю, что мне нужно вернуть все обещания model.create в mainPromise, как массив обещаний, но я не знаю, как лучше всего это сделать. Есть предложения?

PS: CalculateCost, который вызывается внутри createObjects, является асинхронным и работает нормально.

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 голосов
/ 07 ноября 2018

В вашем коде нет ничего, следящего за результатом cost().then(...), так что часть кода запускается и забывается. То же самое верно для вашего звонка на models.Billing.create и одного из then в верхней части вашего кода. Вот почему вы видите результат, который вы есть. При использовании обещаний будьте в поиске мест, где вы создаете обещания и не возвращаете их более высокому абоненту. Это часто предполагает создание обещания, за которым не следят.

Чтобы это исправить:

Прежде всего, исправьте 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.

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 - используйте map и Promise.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.all с небольшим количеством async / await:

Действует так же, как вариант 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))
    );
}
...