Reputation: 45
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:
[id, id, id]
{id, name}
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
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
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