Maybe I just don't understand promises but I've used this pattern before and never had issues. Using bluebird within node.
I have this function which is getting hit:
function getStores() {
return new Bluebird((resolve, reject) => {
return Api.Util.findNearbyStores(Address,(stores) => {
if (!stores.result) {
console.log('one')
reject('no response');
console.log('two')
}
const status = stores.results.status
})
})
}
then hits both of my logs, continues past the if and throws
'one'
'two'
TypeError: Cannot read property 'Status' of undefined
Basically it keeps going right past the resolve.
My impression was the promise should immediately short circuit on reject and pass the rejection through as the resolution to the promise. Am I misunderstanding this?
Yes, you are misunderstanding this. reject(…) is not syntax (just like resolve(…) isn't either) and does not act like a return statement that exits the function. It's just a normal function call that returns undefined.
You should be using
if (!stores.result) reject(new Error('no response'));
else resolve(stores.results.status);
The "short-circuiting" behaviour of rejections is attributed to promise chains. When you have
getStores().then(…).then(…).catch(err => console.error(err));
then a rejection of the promise returned by getStores() will immediately reject all the promises in the chain and trigger the catch handler, ignoring the callbacks passed to then.
Related
Assume an Express route that makes a call to Mongoose and has to be async so it can await on the mongoose.find(). Also assume we are receiving XML but we have to change it to JSON, and that also needs to be async so I can call await inside of it.
If I do this:
app.post('/ams', async (req, res) => {
try {
xml2js.parseString(xml, async (err, json) => {
if (err) {
throw new XMLException();
}
// assume many more clauses here that can throw exceptions
res.status(200);
res.send("Data saved")
});
} catch(err) {
if (err instanceof XML2JSException) {
res.status(400);
message = "Malformed XML error: " + err;
res.send(message);
}
}
}
The server hangs forever. I'm assuming the async/await means that the server hits a timeout before something concludes.
If I put this:
res.status(200);
res.send("Data saved")
on the line before the catch(), then that is returned, but it is the only thing every returned. The client gets a 200, even if an XMLException is thrown.
I can see the XMLException throw in the console, but I cannot get a 400 to send back. I cannot get anything I that catch block to execute in a way that communicates the response to the client.
Is there a way to do this?
In a nutshell, there is no way to propagate an error from the xml2js.parseString() callback up to the higher code because that parent function has already exited and returned. This is how plain callbacks work with asynchronous code.
To understand the problem here, you have to follow the code flow for xml2js.parseString() in your function. If you instrumented it like this:
app.post('/ams', async (req, res) => {
try {
console.log("1");
xml2js.parseString(xml, async (err, json) => {
console.log("2");
if (err) {
throw new XMLException();
}
// assume many more clauses here that can throw exceptions
res.status(200);
res.send("Data saved")
});
console.log("3");
} catch (err) {
if (err instanceof XML2JSException) {
res.status(400);
message = "Malformed XML error: " + err;
res.send(message);
}
}
console.log("4");
});
Then, you would see this in the logs:
1 // about to call xml2js.parseString()
3 // after the call to xml2js.parseString()
4 // function about to exit
2 // callback called last after function returned
The outer function has finished and returned BEFORE your callback has been called. This is because xml2js.parseString() is asynchronous and non-blocking. That means that calling it just initiates the operation and then it immediately returns and the rest of your function continues to execute. It works in the background and some time later, it posts an event to the Javascript event queue and when the interpreter is done with whatever else it was doing, it will pick up that event and call the callback.
The callback will get called with an almost empty call stack. So, you can't use traditional try/catch exceptions with these plain, asynchronous callbacks. Instead, you must either handle the error inside the callback or call some function from within the callback to handle the error for you.
When you try to throw inside that plain, asynchronous callback, the exception just goes back into the event handler that triggered the completion of the asynchronous operation and no further because there's nothing else on the call stack. Your try/catch you show in your code cannot catch that exception. In fact, no other code can catch that exception - only code within the exception.
This is not a great way to write code, but nodejs survived with it for many years (by not using throw in these circumstances). However, this is why promises were invented and when used with the newer language features async/await, they provide a cleaner way to do things.
And, fortunately in this circumstance xml2js.parseString() has a promise interface already.
So, you can do this:
app.post('/ams', async (req, res) => {
try {
// get the xml data from somewhere
const json = await xml2js.parseString(xml);
// do something with json here
res.send("Data saved");
} catch (err) {
console.log(err);
res.status(400).send("Malformed XML error: " + err.message);
}
});
With the xml2js.parseString() interface, if you do NOT pass it a callback, it will return a promise instead that resolves to the final value or rejects with an error. This is not something all asynchronous interfaces can do, but is fairly common these days if the interface had the older style callback originally and then they want to now support promises. Newer interfaces are generally just built with only promise-based interfaces. Anyway, per the doc, this interface will return a promise if you don't pass a callback.
You can then use await with that promise that the function returns. If the promise resolves, the await will retrieve the resolved value of the promise. If the promise rejects, because you awaiting the rejection will be caught by the try/catch. FYI, you can also use .then() and .catch() with the promise, but in many cases, async and await are simpler so that's what I've shown here.
So, in this code, if there is invalid XML, then the promise that xml2js.parseString() returns will reject and control flow will go to the catch block where you can handle the error.
If you want to capture only the xml2js.parseString() error separately from other exceptions that could occur elsewhere in your code, you can put a try/catch around just it (though this code didn't show anything else that would likely throw an exception so I didn't add another try/catch). In fact, this form of try/catch can be used pretty much like you would normally use it with synchronous code. You can throw up to a higher level of try/catch too.
A few other notes, many people who first start programming with asynchronous operations try to just put await in front of anything asynchronous and hope that it solves their problem. await only does anything useful when you await a promise so your asynchronous function must return a promise that resolves/rejects when the asynchronous operation is complete for the await to do anything useful.
It is also possible to take a plain callback asynchronous function that does not have a promise interface and wrap a promise interface around it. You pretty much never want to mix promise interface functions with plain callback asynchronous operations because error handling and propagation is a nightmare with a mixed model. So, sometimes you have to "promisify" an older interface so you can use promises with it. In most cases, you can do that with util.promisify() built into the util library in nodejs. Fortunately, since promises and async/await are the modern and easier way to do asynchronous things, most newer asynchronous interfaces in the nodejs world come with promise interfaces already.
You are throwing exceptions inside the callback function. So you cant expect the catch block of the router to receive it.
One way to handle this is by using util.promisify.
try{
const util = require('util');
const parseString = util.promisify(xml2js.parseString);
let json = await parsestring(xml);
}catch(err)
{
...
}
exports.handler = async (event, context, callback) => {
try {
const { headers, body } = event;
//This is where I forgot the "await" keyword
const input = ValidateInput(body); //Returns Promise
callback(null, true);
}catch(err){
console.log(err);
callback(null, false);
}
}
When calling a function that returns a promise and forgetting to create an await expression promise function call, and that function rejects the promise, Lambda logs this error in cloudwatch
(node:1) UnhandledPromiseRejectionWarning: #<Object>
The fix is simple, don't forget the await expression
const input = await ValidateInput(body); //Return Promise
The fix is simple, don't forget the await expression
const input = await ValidateInput(body); //Return Promise
As already mentioned, the solution is to ensure you await the promise:
const input = await ValidateInput(body);
But I thought I'd add a little context around why this occurs.
Since Promises can be stored in variables and chained at any point, there's no way for the library to know whether or not a Promise chain will have a .catch associated with it some time in the future. Many libraries therefore have a default behaviour of writing to the console if the rejected Promise has not been handled within a number of passes of the event loop; which is why you see this in the log.
You should generally take this warning as implying that you haven't awaited something you should have. As in reality, it's rare that you'd see it on purpose.
The AWS doc explicitly says you don't use callback with an async function.
The third argument, callback, is a function that you can call in
non-async functions to send a response. The callback function takes
two arguments: an Error and a response. The response object must be
compatible with JSON.stringify.
For async functions, you return a response, error, or promise to the
runtime instead of using callback.
So, you may want to fix that in your lambda function.
See here : https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html#nodejs-prog-model-handler-callback
My question: Is it safe to detach the handling from its Promise object?
If I do this ...
var promise1 = new Promise(function(resolve, reject) {
var json = { "counter":0 }
console.log('execute now, worry later ...')
json.counter++;
console.log(json.counter)
resolve(json);
});
var then = function() {
promise1.then(function(value) { console.log('value: '+value.counter) });
}
setTimeout(then, 3000);
var promise2 = new Promise(function(resolve, reject) {
console.log('error thrown here ...')
throw new Error('will it behave the same as with then?');
});
var catchFunc = function() {
promise2.then().catch(function(error) { console.log('error: '+error.message) });
}
setTimeout(catchFunc, 3000);
Then I get warnings that make sense ...
execute now, worry later ...
1
error thrown here ...
(node:9748) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: will it behave the same as with then?
(node:9748) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
value: 1
error: will it behave the same as with then?
Background: In some cases, I want my promises to run concurrently. In some cases, I would like them to be run sequentially. The simplest implementation I've found is to wrap both variants into a function, push it in a map, and treat the "then / catch" in a reduce. The result is that my handling in the first variant (concurrently) is at the time of the wrapping and detached as in my sample above.
If it is safe, how can I remove the warnings from my log?
Rejected promise should be chained with catch on same tick. If this doesn't happen, UnhandledPromiseRejectionWarning appears. It's expected to become an exception in next Node versions, so it's should be avoided.
Once promise control flow was introduced, it's beneficial to use promises. setTimeout stands out and doesn't provide error handling for promises.
If promises are processed concurrently, they are usually handled with Promise.all(...).catch(...). In this case resulting promise is chained with catch on same tick (a similar problem is addressed in this answer).
I need help with debugging this code/or learn efficient way to do it- I tried using bluebird.each to capture all executions within my forEach, but didn't get it work. Same with setting up a new promise with pure javascript. I need help how to execute my forEach FIRST and move on.
let arr = [];
let fields = ['environment', 'habitat', 'earth]
Promise.each(fields, field => {
nano.db.use(field).view('options', 'options')
.spread((body, header) => {
arr.push(body.rows);
})
}).then(() => console.log(arr))
Expected outcome:
arr to console.log ====> [1,2,3,4,5]
Actual outcome:
arr is an empty array ====> [ ]
I see it's a problem with asynchronicity, but I can't seem to figure out how to actually make this work. any input or resources will be greatly appreciated!
I haven't actually ran your code so sorry if I'm incorrect, but from looking at it and the bluebird docs I assume the correction you need to make is return your nano.db call wrapped in a promise inside the Promise.each
let arr = [];
let fields = ['environment', 'habitat', 'earth']
Promise.each(fields, field => {
return new Promise ((resolve, reject) => {
nano.db.use(field).view('options', 'options')
.spread((body, header) => {
arr.push(body.rows);
resolve();
})
});
}).then(() => console.log(arr))
I believe your assumption is right that you're having a problem with asynchronicity when getting an empty array back instead of what you expect. I'm assuming the .then method is firing before the nano.db gets back with the data.
I wrapped your call of nano.db in a promise so that it will await nano.db finishing since Promise.each supports returning promises inside it.
Bluebird's promise docs state with Promise.each.
If the iterator function returns a promise or a thenable, then the
result of the promise is awaited before continuing with next
iteration.
So, if a promise is not returned in your Promise.each and anything that is asynchronous happens inside then just as with a then or a catch method on a promise under the same circumstances.
As I do not know bluebird there may be a way to change that promise to be more bluebird like. The promise I wrapped around the nano.db call is just a normal es6 promise which bluebird may or may not have a different api for creating promises.
I'm trying to reject a promise, and it seems to be working, but not quite as expected.
Parse.Promise.as(1).then(function() {
var vendor = user.get('vendor');
if (vendor)
return vendor.fetch();
else
return Parse.Promise.error("No vendor found");
}, function() {
//specific promise error for this particular promise
res.redirect('/vendor/signup');
}).then(function(result) {
var vendor = result;
res.render('vendor/dashboard.ejs', {
'user': user,
'vendor': vendor
});
}).fail(function(error) {
//general catch all error controller
res.render('error.ejs', {
'error': error
});
});
If the promise fails in the first section where it tries to load the vendor, I want the error for redirect. Instead it's falling through to the ending fail. What's the proper way to do this? Isn't the second function passed to then supposed to be the one that it falls through to if it exists?
The basic signature of .then is .then(onFulfilled,onRejected) - this means it takes the promise it is called on and calls the handlers based on the resolution of that promise:
p.then(function onFulfilled(){
// this gets called only when p fulfills.
},function(){
// this gets called only when p is rejected, if the above onFulfilled
// rejects, this doesn't get called, instead it'll propagate
});
Generally, attach a .then(null,function(){ handler to hand;e exceptions in a code segment:
p.then(function onFulfilled(){
// this gets called only when p fulfills.
}).then(null,function(){ // <- note the .then
// this gets called only when the above promise _including_ the onFulfilled
// handler rejects. Since now the code is `.then`ing the result of the promise
// created by the first .then handler.
});
Also - use .catch in libraries that support it for clarity, unfortunately that's not parse.com promises.