The following snippet of code is a nodeJS controller method calling a service that returns a deferred promise. Im trying to understand the best way to handle multiple exist points.
For example, if the service returns an empty object I want the promise chain to exist and return the response 'Nothing found here' to the user. If it does find something, it moves from step 1 onto the next item in the promise chain i.e. step 2.
From my testing, it seems to be returning the json response and then dropping in to the next logic step i.e. step 2. This logic cant be handled in the service right now i.e. if no item is found to return an error.
module.exports.show = function (req, res) {
service.getItem(itemId)
.then(function (item) {
if (!item) {
return res.json('Nothing found here');
}
// Step 1
// do some work on the item
return item;
})
.then(function (foundItem) {
// Step 2
// do some more work on the item
return res.json('Item has changed ' + foundItem);
})
.catch(function (err) {
return res.json(err);
});
};
Try the following code. The Error is caught by the catch handler. I also changed the if-clause (I assumed that's what you actually meant):
module.exports.show = function (req, res) {
service.getItem(itemId)
.then(function (item) {
if (!item) {
throw new Error('Nothing found here');
}
// Step 1
// do some work on the item
return item;
})
.then(function (foundItem) {
// Step 2
// do some more work on the item
return res.json('Item has changed ' + foundItem);
})
.catch(function (err) {
return res.json(err);
});
};
Remember that then always returns a promise. If you don't return a promise yourself, a resolved promise is automatically created with the return value.
So, return res.json('Nothing found here') is the same as return Promise.resolve(res.json('Nothing found here'));, which means that next then will, obvisouly, be called.
If you don't want to execute the next then, you just have to reject the promise :
throw new Error('Nothing found here'));
// ...
.catch(function (err) {
return res.json(err.message);
});
By the way you probably meant if (!item), not if (item) :
if (!item) {
throw new Error('Nothing found here');
}
If Steps 1 and 2 are synchronous, then :
module.exports.show = function (req, res) {
service.getItem(itemId).then(function (item) {
if (!item) {
throw new Error('Nothing found here'); // this will be caught below
}
item = doWork_2(doWork_1(item)); // Steps 1 and 2:
return res.json('Item has changed ' + item);
}).catch(function (err) {
return res.json(err.message);
});
};
where doWork_1() and doWork_2() both return the processed item.
If Steps 1 and 2 are asynchronous, then :
module.exports.show = function (req, res) {
service.getItem(itemId).then(function (item) {
if (!item) {
throw new Error('Nothing found here'); // this will be caught below
} else {
return doWork_1(item).then(doWork_2); // Step 1, then Step 2
}
})
.then(function (item) {
return res.json('Item has changed ' + item);
}).catch(function (err) {
return res.json(err.message);
});
};
where doWork_1() and doWork_2() return a promise resolved with the processed item.
If it is uncertain whether doWork_1() and .doWork_2() are synchronous or asynchronuus, then use the following pattern, which will handle both eventualities :
module.exports.show = function (req, res) {
service.getItem(itemId).then(function (item) {
if (!item) {
throw new Error('Nothing found here'); // this will be caught below
} else {
return item;
}
})
.then(doWork_1) // Step 1
.then(doWork_2) // Step 2
.then(function (item) {
return res.json('Item has changed ' + item);
}).catch(function (err) {
return res.json(err.message);
});
};
Related
I have an async method named generateSession. And I want the promises system to wait till the call is done. I wait till the data is there and than delete the row at the database. For now, that doesn't make any sense.
But I get an error at this state. Function returned undefined, expected Promise or value It looks like this comes from calling the generateSession. But I don't know how to fix it.
exports.pending = functions.database
.ref('/groups/{groupId}/status/pending/{deviceId}/')
.onCreate(event => {
generateSession().then(function(data) {
console.log("generated session:" + data.sessionId);
return event.data.ref.set({})
}).catch(function(err) {
console.log("error:", err);
});
});
var generateSession = function(){
// *** Return a promise
return new Promise(function(resolve, reject) {
opentok.createSession({mediaMode:"relayed"}, function(err, session){
if (err) {
reject(err);
} else {
resolve(session);
}
});
});
};
The onCreate of Firebase Functions itself needs to return a Promise:
exports.pending = functions.database
.ref('/groups/{groupId}/status/pending/{deviceId}/')
.onCreate(event => {
// You should return the result of generateSession() here
return generateSession().then(function(data) {
console.log("generated session:" + data.sessionId);
return event.data.ref.set({})
}).catch(function(err) {
console.log("error:", err);
// You probably don't want to catch here, let the error
// go through so that Cloud Functions can pick it up
})
});
The code is running well until the Promise.all and then is goes right to the catch saying 'then is not defined'.
I've been trying to figure this out without success for hours :(.
Any help are welcome.
Here is a simplified example of code:
// Save
return new Promise((fulfillSave, rejectSave) => {
// Get
this._getObjects().then((object) => {
var promises = [];
// For each value
object.values.forEach((value) => {
promises.push(
// Create promise
new Promise((fulfill, reject) => {
// Create MDB object + assign value
valueMongoDB.value = value;
// Save
valueMongoDB.save((err, results) => {
if (err) {
reject('Error in saving');
} else {
fulfill();
}
});
})
);
});
// Wait for all promises
Promise.all(promises).then(() => {
// Nothing to do
fulfillSave();
}, then((err) => {
// Err
rejectSave(err);
}));
}
}).catch((err) => {
rejectSave(`Error: ${err.message}`);
});
});
Thanks in advance!
Serge.
This is incorrect:
// Wait for all promises
Promise.all(promises).then(() => {
// Nothing to do
fulfillSave();
}, then((err) => {
// ^^^^--------------------------- error here
// Err
rejectSave(err);
}));
It's trying to call a freestanding function called then and pass its return value into the then on the object returned by Promise.all.
I think you're trying to hook up a failure handler. If so, you don't say then, you just supply a second function:
Promise.all(promises).then(() => {
// Nothing to do
fulfillSave();
}, (err) => {
// Err
rejectSave(err);
}));
But of course, since you're not using the result of that chain and you're just passing the single argument your second function receives into rejectSave, you could just pass rejectSave directly:
Promise.all(promises).then(() => {
// Nothing to do
fulfillSave();
}, rejectSave);
If you told us what your overall code is meant to do and what its inputs are, my suspicion is that that code could be a lot simpler. It's common to create and nest promises unnecessarily, and I suspect that's happening here.
For instance, if you just want to do the saves and get back a promise that will resolve when they're all done successfully or reject on the first failure:
return this._getObjects()
.then(objects => Promise.all(objects.map(value => {
return new Promise((resolve, reject) => {
// Create MDB object + assign value
valueMongoDB.value = value;
// Save
valueMongoDB.save((err, results) => {
if (err) {
reject('Error in saving');
} else {
fulfill();
}
});
});
})));
Or if we give ourselves a helper function for the Mongo bit:
function mongoSavePromise(value) {
return new Promise((resolve, reject) => {
// Create MDB object + assign value
valueMongoDB.value = value;
// Save
valueMongoDB.save((err, results) => {
if (err) {
reject('Error in saving');
} else {
fulfill();
}
});
});
}
then:
return this._getObjects()
.then(objects => Promise.all(objects.map(mongoSavePromise)));
Avoid the Promise constructor antipattern!
Your whole code should be a simple
return this._getObjects().then(object => {
var promises = object.values.map(value => {
// Create MDB object + assign value
valueMongoDB.value = value;
// Save
return valueMongoDB.save().catch(err => {
throw 'Error in saving';
});
});
// Wait for all promises
return Promise.all(promises);
}, err => {
throw `Error: ${err.message}`;
});
No unnecessary callbacks, no room for mistakes. Btw, you shouldn't throw strings.
I've studied several related questions & answers and still can't find the solution for what I'm trying to do. I'm using Mongoose with Bluebird for promises.
My promise chain involves 3 parts:
Get user 1 by username
If user 1 was found, get user 2 by username
If both user 1 and user 2 were found, store a new record
If either step 1 or step 2 fail to return a user, I don't want to do step 3. Failing to return a user, however, does not cause a database error, so I need to check for a valid user manually.
I can use Promise.reject() in step 1 and it will skip step 2, but will still execute step 3. Other answers suggest using cancel(), but I can't seem to make that work either.
My code is below. (My function User.findByName() returns a promise.)
var fromU,toU;
User.findByName('robfake').then((doc)=>{
if (doc){
fromU = doc;
return User.findByName('bobbyfake');
} else {
console.log('user1');
return Promise.reject('user1 not found');
}
},(err)=>{
console.log(err);
}).then((doc)=>{
if (doc){
toU = doc;
var record = new LedgerRecord({
transactionDate: Date.now(),
fromUser: fromU,
toUser: toU,
});
return record.save()
} else {
console.log('user2');
return Promise.reject('user2 not found');
}
},(err)=>{
console.log(err);
}).then((doc)=>{
if (doc){
console.log('saved');
} else {
console.log('new record not saved')
}
},(err)=>{
console.log(err);
});
Example
All you need to do is something like this:
let findUserOrFail = name =>
User.findByName(name).then(v => v || Promise.reject('not found'));
Promise.all(['robfake', 'bobbyfake'].map(findUserOrFail)).then(users => {
var record = new LedgerRecord({
transactionDate: Date.now(),
fromUser: users[0],
toUser: users[1],
});
return record.save();
}).then(result => {
// result of successful save
}).catch(err => {
// handle errors - both for users and for save
});
More info
You can create a function:
let findUserOrFail = name =>
User.findByName(name).then(v => v || Promise.reject('not found'));
and then you can use it like you want.
E.g. you can do:
Promise.all([user1, user1].map(findUserOrFail)).then(users => {
// you have both users
}).catch(err => {
// you don't have both users
});
That way will be faster because you don't have to wait for the first user to get the second one - both can be queried in parallel - and you can scale it to more users in the future:
let array = ['array', 'with', '20', 'users'];
Promise.all(array.map(findUserOrFail)).then(users => {
// you have all users
}).catch(err => {
// you don't have all users
});
No need to complicate it more than that.
move your error handling out of the inner chain to the place you want to actual catch/handle it. As i don't have mongo installed, here is some pseudocode that should do the trick:
function findUser1(){
return Promise.resolve({
user: 1
});
}
function findUser2(){
return Promise.resolve({
user: 2
});
}
function createRecord(user1, user2){
return Promise.resolve({
fromUser: user1,
toUser: user2,
});
}
findUser1()
.then(user1 => findUser2()
.then(user2 => createRecord(user1, user2))) // better nest your promises as having variables in your outside scope
.then(record => console.log('record created'))
.catch(err => console.log(err)); // error is passed to here, every then chain until here gets ignored
Try it by changing findUser1 to
return Promise.reject('not found 1');
First, I would recommend using throw x; instead of return Promise.reject(x);, simply for readibility reasons. Second, your error logging functions catch all the errors, that's why your promise chain is continuing. Try rethrowing the errors:
console.log(err);
throw err;
Don't put error logging everywhere without actually handling the error - if you pass an error handler callback you'll get back a promise that will fulfill with undefined, which is not what you can need. Just use
User.findByName('robfake').then(fromUser => {
if (fromUser) {
return User.findByName('bobbyfake').then(toUser => {
if (toUser) {
var record = new LedgerRecord({
transactionDate: Date.now(),
fromUser,
toUser
});
return record.save()
} else {
console.log('user2 not found');
}
});
} else {
console.log('user1 not found');
}
}).then(doc => {
if (doc) {
console.log('saved', doc);
} else {
console.log('saved nothing')
}
}, err => {
console.error("something really bad happened somewhere in the chain", err);
});
This will always log one of the "saved" or "something bad" messages, and possibly one of the "not found" messages before.
You can also use exceptions to achieve this, but it doesn't really get simpler:
var user1 = User.findByName('robfake').then(fromUser => {
if (fromUser)
return fromUser;
else
throw new Error('user1 not found');
});
var user2 = user1.then(() => // omit this if you want them to be searched in parallel
User.findByName('bobbyfake').then(toUser => {
if (toUser)
return toUser;
else
throw new Error('user2 not found');
})
);
Promise.all([user1, user2]).then([fromUser, toUser]) =>
var record = new LedgerRecord({
transactionDate: Date.now(),
fromUser,
toUser
});
return record.save();
}).then(doc => {
if (doc) {
console.log('saved', doc);
} else {
console.log('saved nothing')
}
}, err => {
console.error(err.message);
});
I'm struggling to wrap my head around a nested promise layout where one one object is returned at the end of it. My current code is as follows:
router
router.get(`/${config.version}/event/:id?`, function (req, res, next) {
var event = new Event(req, res, next);
event.getInfo(req.params.id).then((info) => {
res.send(info);
});
});
function
getInfo(id) {
db.main('events').where('id', id).select()
.then((result) => {
if(result.length > 0) {
var event = result[0];
//regular functions
event.status = this.getStatus(id);
event.content = this.getContent(id);
event.price = this.getPrice(id);
//promise functions
var users = this.getUsers(id);
var hosts = this.getHosts(id);
Promise.all([users, hosts]).then(values => {
event.users = values[0];
event.hosts = values[1];
//return whole event object to router
return event;
})
.catch((err) => {
return {
result: 'error',
error: err
};
});
} else {
return {
result: 'error',
error: "Event does not exist"
};
}
}).catch((e) => {
return {
result: 'error',
error: "Could not retrieve event info"
};
});
}
As you can see, the router initiates a call to get info about an event. The function then does a database call and gets some event data. Thereafter I need to get the users and hosts of the event from a different table, append that info to the event object as well and then return the whole object to the router to be sent to the client.
When I do this I get an error because I'm not returning a promise from the getInfo function, but I'm not sure how or which promise I'm supposed to return.
I'd appreciate some help with this. Thanks
using .then means that you are returning a promise.
function getInfo(id) {
return new Promise(function(resolve, reject) {
resolve('yay!');
})
}
getInfo().then(function(result) { //result = yay! });
to make your code work, simply replace all the returns with resolves, the errors with rejects, and wrap the whole thing with a return new Promise as i did.
getInfo(id) {
return new Promise(function(resolve, reject) {
db.main('events').where('id', id).select()
.then((result) => {
if (result.length > 0) {
var event = result[0];
//regular functions
event.status = this.getStatus(id);
event.content = this.getContent(id);
event.price = this.getPrice(id);
//promise functions
var users = this.getUsers(id);
var hosts = this.getHosts(id);
Promise.all([users, hosts]).then(values => {
event.users = values[0];
event.hosts = values[1];
//return whole event object to router
resolve(event);
})
.catch((err) => {
reject({
result: 'error',
error: err
});
});
} else {
reject({
result: 'error',
error: "Event does not exist"
});
}
}).catch((e) => {
reject({
result: 'error',
error: "Could not retrieve event info"
});
});
});
}
Just wrap your async code in Promise like this:
getInfo(id) {
return new Promise(function(resolve, reject) {
db.main('events').where('id', id).select()
.then((result) => {
//...
resolve(/* result */)
// OR
reject(/* Error */)
})
}
Note: Use resolve and reject instead return
It's a combination of several things, but the main one is that you are never returning anything from getInfo, so your router handler is calling .then on undefined.
Do not call .catch (without throwing inside it) on Promises you intend to return for a caller to consume. This makes it not possible to use .catch, because you recovered the Promise chain into a resolved one.
Whatever you return inside a .then will be merged into the promise chain, so it's not actually a "Promise that resolves with a Promise". Your whole code could be replaced with:
getInfo (id) {
return db.main('events').where('id', id).select()
.then(result => {
if (result.length == 0) {
// you can also just throw your error object thing,
// but standard Error are generally the convention
throw new Error('Event does not exist')
}
const [event] = result
event.status = this.getStatus(id)
event.content = this.getContent(id)
event.price = this.getPrice(id)
return Promise.all([this.getUsers(id), this.getHosts(id)])
.then(([users, hosts]) => {
event.users = users
event.hosts = hosts
// this is the only value that
// this.getInfo(id).then(value => {/* ... */}) will see
return event
}
})
}
I am building this promise chain. The goal is to have the first action check for uniqueness on a field in the DB, and then if unique, save the object. But if the object is not unique, it should not save, and should return an error response.
function(request, reply) {
var payload = request.payload;
checkThatEmailDoesNotExist().then(saveUser)
function checkThatEmailDoesNotExist() {
return User.where({email: payload.email}).countAsync()
.then(function(count) {
if (count > 0) {
throw Boom.badRequest('The email provided for this user already exists')
}
return null;
})
.catch(function(err) { // ~This catch should stop the promise chain~
reply(err);
})
}
function saveUser() {
// ~But instead it is continuing on to this step~
return User.massAssign(request.payload).saveAsync()
.spread(function(user, numAffected) {
return reply(user);
})
.catch(function(err) {
server.log(['error', 'api', 'auth'], err);
throw Boom.badRequest('Object could not be saved to database');
});
}
}
If an error is thrown in the checkThatEmailDoesNotExist() it's catch() should return the error, and stop processing the rest of the original promise chain.
Instead of acting that way, the catch() fires, and then continues to move on to the saveUser() function.
You are mixing promises and callbacks which is a horrible anti-pattern. The caller will simply use
the returned promise, there is no need to manually wire things back to callbacks.
function save(request) {
var payload = request.payload;
return User.where({email: payload.email}).countAsync()
.then(function(count) {
if (count > 0) {
throw Boom.badRequest('The email provided for this user already exists')
}
return User.massAssign(request.payload).saveAsync()
})
.get(0)
/* equivalent to
.spread(function(user, numAffected) {
return user;
}) */
.catch(Promise.OperationalError, function(err) {
server.log(['error', 'api', 'auth'], err);
throw Boom.badRequest('Object could not be saved to database');
});
}
Usage:
save(request).then(function(user) {
response.render(...)
}).catch(function(e) {
response.error(...)
})
If you wanted to expose a callback api, the sane way to do that is to bolt on a nodeify at the end of an existing promise api and call it a day:
function save(request, callback) {
var payload = request.payload;
return User.where({email: payload.email}).countAsync()
.then(function(count) {
if (count > 0) {
throw Boom.badRequest('The email provided for this user already exists')
}
return User.massAssign(request.payload).saveAsync()
})
.get(0)
/* equivalent to
.spread(function(user, numAffected) {
return user;
}) */
.catch(Promise.OperationalError, function(err) {
server.log(['error', 'api', 'auth'], err);
throw Boom.badRequest('Object could not be saved to database');
})
.nodeify(callback);
}
save(request, function(err, user) {
if (err) return response.error(...);
response.render(...);
});