insane
insane

Reputation: 45

Need advice on nested Promises.all

Playing around with modern JS and got stuck a little with the following.

Consider having an ExtSystem accessible via some HTTP API and local Mongo instance. They both contain objects with name and id.

For Mongo I'm using mongoose with ObjectSchema model ({_id, sourceId, name, internalParam}) where sourceId equal to id from ExtSystem and internalParam exists only in my app. For ExtSystem there is 2 methods returning request.js Promise:

There is also a global function errHandler which can handle errors from both request.js and mongoose Promises. The goal to achieve is to sync Mongo with ExtSystem: update all objects from ExtSystem in Mongo and delete no longer present in ExtSystem ones from Mongo.

What I came up with:

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);

So I still have 4 nested Promise resolves and probably missing something. Is there a best way to achieve this with less complex code?

Upvotes: 1

Views: 74

Answers (2)

Derek 朕會功夫
Derek 朕會功夫

Reputation: 94379

Promises are designed to get rid of the pyramids of doom. If you have nested Promises then you are doing it wrong.

Promises allow you to return another promise inside the call in order to chain them. So instead of doing:

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

You should do

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

If you have some variables that you need to access in the future promises, you can either include them as another promise, or use this piece of code I created a while ago that create a promise that contains a global reusable object.

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

Upvotes: 3

Ben Fortune
Ben Fortune

Reputation: 32137

You can return a Promise from a thenable to chain it. Because it can chain, that means all your errors can be propagated to one handler.

Your code could essentially become:

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);

Upvotes: 1

Related Questions