Reputation: 833
contrived example
Say we need to construct a Foo
and Bar
for a User
in some Node application. We are expected to implement the constructFooForUser
function, which must invoke some callback
function with either
Error
Foo
, and the created Bar
To do this, we must make use of some database functions which fetch/create our objects, returning a Promise.
var constructFooForUser = function(userId, data, callback) {
db.users.find(userId)
.then(function(user) {
if (!user) { return callback(new Error('user not found')); }
db.foos.create(user, data)
.then(function(foo) {
db.bars.create(user, foo, data)
.then(function(bar) {
callback(null, foo, bar);
})
.catch(function(err) {
callback(err);
});
})
.catch(function(err) {
callback(err);
});
})
.catch(function(err) {
callback(err);
});
};
Is this the correct way to structure this type of promise based code?
I've seen examples of Promise code which looks like
doSomething()
.then(doSomethingElse)
.then(doYetAnotherThing)
.then(doLastThing)
.catch(handleError);
but I don't think this works in this scenario, as I need user, foo, and bar at the same time, and scope isn't shared between the chained functions.
I ask because the code looks repetitive, so I am wondering if something is unusual.
EDIT: Forgot to include foo
in the creation of bar
Upvotes: 3
Views: 77
Reputation: 12677
foo and bar don't seem to depend on each other, so we could make this part paralell:
var constructFooForUser = function(userId, data, callback) {
db.users.find(userId).then(function(user) {
//because rejecting a promise means nothing else than throwing an Error
if(!user) throw new Error('user not found');
//resolves as soon as both requests have been resolved.
//rejects if _any_ of the requests is rejecting
return Promise.all([
db.foos.create(user, data),
db.foos.create(user, data)
]);
}).then(
([foo, bar]) => callback(null, foo, bar), //utilizing array destructuring
(err) => callback(err)
);
}
I'm using .then(a, b)
instead of .then(a).catch(b)
, because the latter would also catch Errors that are thrown in your callback-function (and we don't want that); that's your responsibility to deal with them. It would hide bugs in your code, by error-messages simply not showing up in the console.
Even your attempt with the nested promises could be streamlined by returning the promises and concatenating at least the error-part
var constructFooForUser = function(userId, data, callback) {
db.users.find(userId)
.then(function(user) {
if (!user) throw new Error('user not found');
return db.foos.create(user, data)
.then( foo => db.bars.create(user, data).then( bar => callback(null, foo, bar) ) );
})
.catch(function(err) {
callback(err);
});
};
Upvotes: 0
Reputation: 22040
I typically (and yes I consider this a hacky workaround) accumulate the results in an array as I go.
firstThing()
.then(first => Promise.all([first, secondThing]))
.then(([first, second]) => Promise.all([first, second, thirdThing()]))
.catch(e => handleErr(e));
This accumulation pattern works, and with ES 2015 destructuring its not too clunky.
Upvotes: 1
Reputation: 74106
How about storing foo
in some variable until we need it?
var constructFooForUser = function(userId, data, callback) {
var gFoo;
db.users.find(userId)
.then(function(user) {
if (!user) { return callback(new Error('user not found')); }
return db.foos.create(user, data)
})
.then(function(foo) {
gFoo = foo;
return db.bars.create(user, data)
})
.then(function(bar) {
callback(null, gFoo, bar);
})
.catch(function(err) {
callback(err);
});
};
Upvotes: 1