Нужен совет по вложенным Promises.all

1

Играя с современными JS и немного застрял со следующим.

Рассмотрите возможность использования ExtSystem через некоторый HTTP API и локальный экземпляр Mongo. Оба они содержат объекты с name и id.

Для Mongo я использую mongoose с моделью ObjectSchema ({_id, sourceId, name, internalParam}), где sourceId равно id из ExtSystem и internalParam существует только в моем приложении. Для ExtSystem существует 2 метода, возвращающих request.js Promise:

  • ExtSystem.all возвращает массив идентификаторов [id, id, id]
  • ExtSystem.get возвращает сам объект {id, name}

Существует также глобальная функция errHandler которая может обрабатывать ошибки как с request.js и с mongoose Promises. Цель состоит в том, чтобы синхронизировать Mongo с ExtSystem: обновить все объекты из ExtSystem в Mongo и удалить больше не в ExtSystem из Mongo.

Что я придумал:

ExtSystem.all().then(body => { 
    let basket = []; // will store all promises for both ExtSystem and Mongo requests
    basket.push(...body.map(o => ExtSystem.get(o.id));
    basket.push(ObjectSchema.find({}, 'sourceId'));

    Promise.all(basket).then(basketDoc => {
        let mongoObjects = {}, extObjects = {};
        basketDoc.pop().forEach(o => mongoObjects[o.sourceId] = o._id); // Mongo retuns array of {_id, sourceId } objects
        basketDoc.forEach(o => { // ExtSystem returns array of {id, name} objects
            extObjects[o.id] = {
                sourceId: o.id,
                name: o.name
            }
        });

        let esSet = new Set(Object.keys(extObjects));
        let mongoDeleteIds = Object.keys(mongoObjects).filter(oId => !esSet.has(oId)); // Set.has is faster than Array.indexOf

        let syncPromises = [];
        syncPromises.push(...Object.keys(extObjects).map(oId => ObjectSchema.findOneAndUpdate({ sourceId: extObjects[oId].sourceId }, extObjects[oId], { upsert: true, new: true })));
        syncPromises.push(...mongoDeleteIds.map(oId => ObjectSchema.remove({_id: oId})));

        Promise.all(syncPromises).then(_ => { // I don't need results, only the moment when sync is complete
            ObjectSchema.find().then(doc => { // return actual objects from Mongo
                someBusinessLogic(doc);
            }).catch(errHandler);
        }).catch(errHandler);
    }).catch(errHandler);
}).catch(errHandler);

Таким образом, у меня все еще есть 4 вложенных Promise, и, вероятно, что-то не хватает. Есть ли лучший способ добиться этого с помощью менее сложного кода?

Теги:
es6-promise

2 ответа

3
Лучший ответ

Обещания направлены на то, чтобы избавиться от пирамид смерти. Если у вас есть вложенные обещания, вы делаете это неправильно.

Обещания позволяют вам вернуть другое обещание внутри вызова, чтобы связать их. Поэтому вместо того, чтобы делать:

p1.then(stuff => {
    p2.then(stuff =>{
        ...
    });
});

Ты должен сделать

p1
.then(stuff => {
    return p2;
}).then(stuff => {
    return;
});

Если у вас есть какие-то переменные, которые вам нужны для доступа в будущих обещаниях, вы можете включить их в качестве другого обещания или использовать этот фрагмент кода, который я создал некоторое время назад, что создает обещание, которое содержит глобальный объект многократного использования.

Promise
.resolve({})           // creates global object for holding values
.then(obj => {
    return pack(obj, taskPromiseA, "a", taskPromiseB, "b");
})
.then(obj => {        // you can access results from A and B here
    return pack(obj, taskPromiseC, "c");
})
.then(console.log);   // you can access them all here
1

Вы можете вернуть Promise из последующего, чтобы связать его. Поскольку это может быть цепочка, это означает, что все ваши ошибки могут быть переданы одному обработчику.

Ваш код может по существу стать:

ExtSystem.all().then(body => {
    let basket = []; // will store all promises for both ExtSystem and Mongo requests
    basket.push(...body.map(o => ExtSystem.get(o.id));
    basket.push(ObjectSchema.find({}, 'sourceId'));
    return Promise.all(basket);
}).then(basketDoc => {
    let mongoObjects = {}, extObjects = {};
    basketDoc.pop().forEach(o => mongoObjects[o.sourceId] = o._id); // Mongo retuns array of {_id, sourceId } objects
    basketDoc.forEach(o => { // ExtSystem returns array of {id, name} objects
        extObjects[o.id] = {
            sourceId: o.id,
            name: o.name
        }
    });

    let esSet = new Set(Object.keys(extObjects));
    let mongoDeleteIds = Object.keys(mongoObjects).filter(oId => !esSet.has(oId)); // Set.has is faster than Array.indexOf

    let syncPromises = [];
    syncPromises.push(...Object.keys(extObjects).map(oId => ObjectSchema.findOneAndUpdate({ sourceId: extObjects[oId].sourceId }, extObjects[oId], { upsert: true, new: true })));
    syncPromises.push(...mongoDeleteIds.map(oId => ObjectSchema.remove({_id: oId})));
    return Promise.all(syncPromises);
}).then(_ => {
    return ObjectSchema.find();
}).then(doc => {
    return someBusinessLogic(doc);
}).catch(errHandler);

Ещё вопросы

Сообщество Overcoder
Наверх
Меню