Catching errors from nested async/await functions - javascript

I have a function chain in a node 4.3 script that looks something like, callback -> promise -> async/await -> async/await -> async/await
like so:
const topLevel = (resolve, reject) => {
const foo = doThing(data)
.then(results => {
resolve(results)
})
.catch(err => {
reject(err)
})
}
async function doThing(data) {
const thing = await doAnotherThing(data)
return thing
}
async function doAnotherThing(data) {
const thingDone = await etcFunction(data)
return thingDone
}
(The reason it isn't async/await all the way through is that the top level function is a task queue library, and ostensibly can't be run async/await style)
If etcFunction() throws, does the error bubble up all the way to the top-level Promise?
If not, how can I bubble-up errors? Do I need to wrap each await in a try/catch and throw from there, like so?
async function doAnotherThing(data) {
try {
await etcFunction(data)
} catch(err) {
throw err
}
}

If etcFunction() throws, does the error bubble up all the way through the async functions?
Yes. The promise returned by the outermost function will be rejected. There's no need to do try { … } catch(e) { throw e; }, that's just as pointless as it would be in synchronous code.
… bubble up all the way to the top-level Promise?
No. Your topLevel contains multiple mistakes. If you don't return the doThing(data) from the then callback, it will be ignored (not even awaited) and the rejection stays unhandled. You'll have to use
.then(data => { return doThing(data); })
// or
.then(data => doThing(data))
// or just
.then(doThing) // recommended
And in general, your function should look like this:
function toplevel(onsuccess, onerror) {
makePromise()
.then(doThing)
.then(onsuccess, onerror);
}
No unnecessary function expressions, no .then(…).catch(…) antipattern (that could lead to onsuccess and onerror to both be called).

I know this question is old, but as your question is written, wouldn't the doAnotherThing() function be unnecessary because it just wraps etcFunction()?
So your code could be simplified to this:
async function topLevel(){
let thing = await doThing(data)
let thingDone = await etcFunction(data)
//Everything is done here...
}
//Everything starts here...
topLevel()

I just had a similar issue where my nested errors didn't appear to bubble up to my top level function.
The fix for me was to remove the "try / catch" from my nested function and allow the error to just be thrown.

Related

Why can't try/catch handle error throw in Promise constructor

This code get's unhandledRejection error and I don't know why.
If the Error is thrown in try/catch, shouldn't it be caught by Catch Expression?
async function main () {
try {
await run(throwError)
} catch (error) {
console.log('main catch error', error);
}
}
async function run (callback) {
return new Promise(async resolve => {
await throwError()
});
}
async function throwError () {
throw new Error('custom error')
}
process.on('unhandledRejection', (reason, promise) => {
console.log('unhandledRejection - reason', reason, promise);
})
main()
It's not caught because you're passing an async function into new Promise. An error inside an async function rejects the promise that the function returns. The Promise constructor doesn't do anything a promise returned by the function you pass it (the return value of that function is completely ignored), so the rejection goes unhandled. This is one of the promise anti-patterns: Don't provide a promise to something that won't handle it (like addEventListener on the web, or the Promise constructor, or forEach, ...).
Similarly, there's no reason to use new Promise in your async function at all. async functions already return promises. That's another anti-pattern, sometimes called the explicit promise construction anti-pattern. (But see below if you're wrapping an old-fashioned callback API.)
If you remove the unnecessary new Promise, it works as you expect (I also updated run to call callback rather than ignoring it and calling throwError directly):
async function main() {
try {
await run(throwError);
} catch (error) {
console.log("main catch error", error);
}
}
async function run(callback) {
return await callback();
}
async function throwError() {
throw new Error("custom error");
}
process.on("unhandledRejection", (reason, promise) => {
console.log("unhandledRejection - reason", reason, promise);
});
main();
About the return await callback(); in that example: Because whatever you return from an async function is used to settle the function's promise (fulfilling it if you return a non-thenable, resolving it to the thenable if you return one), and that return is at the top level of the function (not inside a try/catch or similar), you could just write return callback();. In fact, you could even remove async from run and do that. But keeping the await makes the async stack trace clearer on some JavaScript engines, more clearly indicates what you're doing, and keeps working correctly even if someone comes along later and puts a try/catch around that code.
In a comment, you said that you used new Promise because you were wrapping around a callback-based API. Here's how you'd do that (see also the answers here):
// Assuming `callback` is a Node.js-style callback API, not an
// `async` function as in the question:
function run(callback) {
return new Promise((resolve, reject) => {
callback((err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
but, you don't have to do that yourself if the callback-based API uses the standard Node.js convention as above, there's a promisify function in utils.
You don't want to throw an error in this case, you want to invoke reject:
return new Promise((resolve, reject) => {
reject('custom error')
});
If the error being thrown is out of your control, you can catch it inside the promise implementation and reject in that case.

JavaScript: Using Async/Await in a Promise

On the way of learning the concepts of asynchronous JavaScript, I got struggled with the idea behind the situation when they can be chained. As an example consider the following situation: a webhook calls a cloud function and as a requirement there is set a time interval by which the cloud function should response to the webhook. In the cloud function is called an operation for fetching some data from a database, which can be short- or long-running task. For this reason we may want to return a promise to the webhook just to "register" a future activity and later provide results from it e.g.:
async function main () {
return new Promise ((resolve, reject) => {
try {
db.getSomeData().then(data=> {
resolve({ result: data});
});
} catch (err) {
reject({ error: err.message });
}
});
}
Another way is to use async/await instead of a promise for fetching the data, e.g.:
async function main () {
return new Promise (async (resolve, reject) => {
try {
const data = await db.getSomeData();
resolve({ result: data });
} catch (err) {
reject({ error: err.message });
}
});
}
(The code is a pseudo-code and therefore all checks on the returned types are not considered as important.)
Does the await block the code in the second example and prevent returning of the promise?
async functions always return a Promise. It is up to the function caller to determine how to handle it: either by chaining with then/catch or using await. In your example, there is no need to create and return a new Promise.
You could do something like this for example:
async function main () {
try {
const data = await db.getSomeData()
return {result: data}
} catch (err) {
return {error: err.message}
}
}
// On some other part of the code, you could handle this function
// in one of the following ways:
//
// 1.
// Chaining the Promise with `then/catch`.
main()
.then((result) => console.log(result)) // {result: data}
.catch((err) => console.log(err)) // {error: err.message}
// 2.
// Using await (provided we are working inside an asyn function)
try {
const result = await main()
console.log(result) // {result: data}
} catch (err) {
console.log(err) // {error: err.message}
}
Try to avoid combining both methods of handling asynchronous operations as a best practice.

JS promises: is this promise equivalent to this async/await version?

If I have the following code
new Promise(res => res(1))
.then(val => console.log(val))
is this equivalent to
let val = await new Promise(res => res(1))
console.log(val)
I know one difference is that I have to wrap the second one in an async function, but otherwise are they equivalent?
Because your promise always resolves (never rejects), they are equivalent. You could also do:
Promise.resolve(1).then(val => console.log(val));
Keep in mind that a major difference with await (besides it needs to be wrapped in an async function) is what happens when the promise rejects. Though your example is hard-wired to resolve, not reject, lets look at what they look like with actual error handling (which you should always have):
new Promise(res => res(1))
.then(val => console.log(val))
.catch(err => console.log(err));
And:
try {
let val = await new Promise(res => res(1));
console.log(val);
} catch(e) {
console.log(err);
}
Or, if you didn't have the try/catch, then any rejects would automatically be sent to the promise that was automatically returned by the async function. This automatic propagation of errors (both synchronous exceptions and asynchronous rejections) is very useful in an async function.
It becomes more apparent when you have multiple asynchronous operations in series:
const fsp = require('fs').promises;
async function someFunc() {
let handle = await fsp.open("myFile.txt", "w");
try {
await handle.write(...);
await handle.write(...);
} finally {
await handle.close().catch(err => console.log(err));
}
}
someFunc().then(() => {
console.log("all done");
}).catch(err => {
console.log(err);
});
Here, the async wrapper catches errors form any of the three await statements and automatically returns them all to the caller. The finally statement catches either of the last two errors in order to close the file handle, but lets the error continue to propagate back to the caller.

Most efficient way to ensure sequential commands in Javascript

Root Cause / Fix:
Turns out this was an issue with the specific winston-papertrail lib I am using and that's why it wasn't behaving as expected, even using the .finally() block.
The winston-papertrail examples didn't use end() but found the proper syntax in the upstream Winston lib examples: https://github.com/winstonjs/winston/blob/73ae01f951600306242e00dd0d2b0a85b6d9d254/examples/finish-event.js#L28
Once that was discovered, I was able to just add it to the .finally() block and everything worked fine as defined in the accepted answer
Original Post:
This is specifically talking about Javascript, or more specifically languages that do not execute operations "in-order" (async)
Taking this scenario, where the logger.close() operation must run after the logger.error() statement...
({
//SomePromise
})
.then( //Then something else )
.then(function() {logger.close();})
.catch(err =>
{
logger.error(err);
logger.close();
})
With Javascript, this works fine with try/finally:
.catch(err =>
{
try {
logger.error(err);
}
finally {
logger.close();
}
})
I'm a total newb to async, promises, etc and how to deal with them (I'm a Python guy).
Is there a more ideal way to make this work?
Am I missing an important concept about this or is this method viable and logical?
Promise.prototype.finally()
The Promise api has a finally as well.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
MDN's example:
p.finally(onFinally);
p.finally(function() {
// settled (fulfilled or rejected)
});
Applied to your code
({
//SomePromise
})
.then( //Then something else )
.catch(err => {
logger.error(err);
})
.finally(() => {
logger.close();
});
Have you heard of async await ?
const asyncFunc = async () => { something };
const logger.error = async (err) => { something with err }
try {
var result = await asyncFunc();
} catch (err) {
await logger.error(err);
} finally {
logger.close();
}
So here you will execute the asyncFunc:
If everything goes well, the finally block gets executed and the logger.close is called
If there's an error, the await keyword will wait until that
logger.error is completed and then move to the finally block which will trigger the logger.close

I can't get the value of "result" in Node.js

In my console.log(info), I want to get the value of "result". But when I use console.log(info), I get Promise { <pending> }:
var info=new Promise((resolve, reject) => {
request(options, (err, res, body) => {
if (body.match('success') || body.match('code="25"')) {
resolve("success");
} else {
reject(body);
}
});
}).then(result => {
return result;
}).catch(err => {
console.log("error: " + err)
});
console.log(info);
I would like to get info == result. How can I do it?
Thanks
What happens here
First some explanation of what happens here. When you do something like this:
var info = new Promise((resolve, reject) => {
// ...
}).then(result => {
// ...
}).catch(err => {
// ...
});
then what ends up as the value of the info variable is a promise. That's because you start with a promise returned by the new Promise() constructor, you invoke its .then() method which returns a second promise, and on that second promise you invoke the .catch() method which returns a third promise - and this third promise is what gets saved into the info variable.
All of those method calls return immediately before the original HTTP request is finished so when you reach the next line:
console.log(info);
it's impossible to access the value of the response from the request() call because it didn't happen yet (the request() callback hasn't been called yet at this point). The info variable has a promise which is an object that you can use to register the callbacks that you want run when the value is eventually available. When you pass it to the console.log() it prints that it's a promise.
How to get the value
Now, when you want to print the value when it's finally available then you can attach a promise resolution callback to the promise that you have e.g. with a code like this:
info.then((value) => {
// you can use the value here
console.log('Value:', value);
}).catch((err) => {
// the promise got rejected
console.log('Error:', err);
});
If you are using a relatively recent version of Node (7.0+) then you could use await if you are inside of a function declared with an async keyword, to get the resolution value of the promise like this:
(async function () {
let value = await info;
console.log(value);
})();
or shorter:
(async () => {
console.log(await info);
})();
(If you are already inside of an async function then you don't need the (async () => { ... })(); wrapper.)
How it works
What the await keyword does is it yields the promise from an implicit generator function that passes the control to the outer coroutine control code which attaches a resolution and rejection handlers to that yielded promise and starts the generator again by either returning the resolved value from the await operator or raising an exception if the promise was rejected. The generator can then continue using the return value and catching the exception, or it can let the exception bubble to the outer blocks where it can be caught or converted to a rejection of the implicit promise returned by the async function if not handled along the way.
How you can use it
It may seem complicated but from the point of view of your code it lets you do things like:
let value = await fun();
(where fun() is a function that returns a promise) and have the resolved value of the promise available in the value variable (the real value, not a promise).
Remember that the await keyword throws an exception when the promise in question gets rejected. To handle that case you can use a try/catch block:
try {
let value = await fun();
// promise got resolved, you have value here:
console.log('Value:', value);
} catch (error) {
// promise got rejected, you have error here:
console.log('Error:', error);
}
which is equivalent of this code without the new syntax:
fun().then((value) => {
// promise got resolved, you have value here:
console.log('Value:', value);
}).catch((error) => {
// promise got rejected, you have error here:
console.log('Error:', error);
});
with the main difference being the variable scoping when you await on multiple promises in a single try block as opposed to using multiple chained .then() handlers, each returning a new promise.
Your code looked like it used await but it didn't
I added the example of how to fix your code with await because you wrote your code as if this:
var info = new Promise(...);
// here the 'info' variable is set to a promise
was really this:
var info = await new Promise(...);
// here the 'info' variable is set to the value
// that the promise eventually resolves to
More info
For more info, see:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
Node support
For support of that syntax in Node versions, see:
http://node.green/#ES2017-features-async-functions
In places where you don't have native support for async and await you can use Babel:
https://babeljs.io/docs/plugins/transform-async-to-generator/
or with a slightly different syntax a generator based approach like in co or Bluebird coroutines:
https://www.npmjs.com/package/co
http://bluebirdjs.com/docs/api/promise.coroutine.html
In your code, info is a promise. Thus, you don't compare info to anything. To get access to the result in a promise, you use .then() on the promise as in:
info.then(result => {
// test result here
}).catch(err => {
// handle error here
});
In addition, your .catch() handler is "eating" the error after logging it. If you don't rethrow the error or return a rejected promise from a .catch() handler, then the error is considered "handled" and the promise state changes to fulfilled.
So, you can do this:
let info = new Promise((resolve, reject) => {
request(options, (err, res, body) => {
if (body.match('success') || body.match('code="25"')) {
resolve("success");
} else {
reject(body);
}
});
}).catch(err => {
console.log("error: " + err);
// rethrow error so promise stays rejected
throw err;
});
info.then(result => {
// test result here
}).catch(err => {
// handle error here
});

Categories

Resources