Proper way of handling error in multi await statements - javascript

In my code I have this statement (which works as intended):
async initializeConfiguration(fileName, projectPath) {
let encryptedConfigurationFile = await this.getEncryptedConfigurationFromS3(fileName);
let decryptedConfigurationFile = await this.decryptConfigurationObject(encryptedConfigurationFile);
return await this.writeConfigurationObjectToFileSystem(fileName, decryptedConfigurationFile, projectPath);
}
I want to properly handle any possible error that might come from any of the await operations. I thought something like this:
try {
let encryptedConfigurationFile = await this.getEncryptedConfigurationFromS3(fileName);
let decryptedConfigurationFile = await this.decryptConfigurationObject(encryptedConfigurationFile);
return await this.writeConfigurationObjectToFileSystem(fileName, decryptedConfigurationFile, projectPath);
} catch (error) {
console.error(`Something bad happened: ${error}`);
}
But I am not sure. Is a single catch going to cover all three awaits?

Yes, it will, just like try/catch around multiple non-awaits statements in a non-async function handles exceptions from all of those statements.
Note that your updated code as shown (if I assume it's still within initializeConfiguration) converts errors into resolutions with undefined, which makes it difficult to use initializeConfiguration unless nothing uses its result. If something does use its result, handle errors there, rather than in initializeConfiguration itself.

Related

How to catch an error thrown in an un-awaited async function inside try/ catch?

I'm hoping someone can suggest a better method for what I'm trying to achieve.
While performing a rather long and complicated webflow using puppeteer, occasionally an error will occur that disrupts the actions I'm trying to take on the page. There's nothing I can do about it in these situations other than return a useful error. However it doesn't happen often enough to explicitly wait and try to catch it after each step in my code.
The solution I have is:
async function runCode() {
try {
const page = browser.open(url)
listenForError(page)
await longWebFlow()
} catch (err) {
return err.message
}
}
async function listenForError(page) {
await page.waitForXPath(errorMessageXPath)
throw new Error('Error found!')
}
try {
await runCode()
} catch (err) {
console.log(err.message)
// should print('Error found')
}
Obviously, the unawaited listenForError call won't be caught in the try/catch, but I also cant await the call, or else I'll never get to the main part of the code.
The code works to short-circuit the process and return, since the error occurred, but how can I refactor this to catch the error message?
It seems like you want to wait until either the error is thrown or the longWebFlow() finishes - basically doing both concurrently. That's a perfect use case for promises with Promise.race:
async function runCode() {
const page = browser.open(url)
await Promise.race([
listenForError(page),
longWebFlow(),
]);
}
You could also use Promise.all if you made longWebFlow cancel the listenForError and fulfill the promise.
Either way, since you can properly await the promises now, the error also will be caught in the try/catch, as it should.
If you can't await the async operation then the only other "follow up" is with callbacks like .then() and .catch(). For example, you can catch the error here:
listenForError(page).catch(e => console.log(e));
This won't await the operation, it's just supplying a callback for whenever that operation fails at whatever point in the future.

Why am I not getting "Redundant return await" (ESLint: no-return-await) warning for all the below code snippets?

I have below code paths which are giving the Redundant return await warning in "IntelliJ": (https://eslint.org/docs/rules/no-return-await)
Note: I see -ve votes for my question. Most likely I am missing something basic - can you point out in comments/question what am I missing? I am spending a significant amount of time asking the question - it will be helpful if you add details for -ve votes.
import {browser, By, element, promise, protractor} from 'protractor';
async randomMethod(): Promise<string> {
return await element.all(By.css('.random-class')).first().getText();
}
async randomMethod4() {
return await browser.driver.get(this.url);
}
Please note that just removing the await in the above methods get rid of the warning (like following code snippet). But my question is why is it ok for the below methods to do "return await", but not for "above".
async randomMethod(): Promise<string> {
return element.all(By.css('.random-class')).first().getText();
}
Whereas the following code paths don't give the same warning:
async randomMethod2(): Promise<number> {
return await element.all(By.css('.mat-row')).count();
}
async randomMethod3() {
return await element.all(By.css('.random-class')).first();
}
I am trying to understand in detail the reason for warning even though my tests are passing as I may be missing something subtle here. Any thoughts are helpful.
return await element.all(By.css('.random-class')).first().getText();
Btw, following are the function definitions/prototypes:
first(): ElementFinder;
count(): wdpromise.Promise<number>;
getText(): promise.Promise<string>;
get(url: string): promise.Promise<void>;
async functions always return a Promise, even if you don't await inside them. Since they always return a Promise you always have to "unwrap them" at the calling site (either by using await or .then()) in order to get the value or do something when it resolves.
When you use return await you are "uwrapping" a promise only for the value to be "wrapped" again (since you are inside an async function), so it is always redundant.
It does not affect the behavior of your code, which is why your tests still pass.
Edit:
After reading a bit on the no-return-await rule from eslint, turns out there is one case in which it is not redundant, which is inside a try-catch block.
async function foo() {
try {
return await bar();
} catch (error) {}
}
the await is necessary to be able to catch errors thrown from bar().

What is the proper way to handle errors in a promise?

I've been seeing a couple of different patterns for handling errors.
One is like:
let result;
try {
result = await forSomeResult(param);
} catch {
throw new Error();
}
And the other is like:
const result = await forSomeResult(param).catch(() => throw new Error());
I prefer the latter since it looks like a cleaner solution. But I've also heard talks that the first solution is better since there could be some race condition where the .catch doesn't execute before the next command is run.
I was wondering if someone had a technical answer about why one method is better than the other.
First of all, there's no reason to catch and throw or .catch() and throw unless you're going to do something else inside the catch handler or throw a different error. If you just want the same error thrown and aren't doing anything else, then you can just skip the catch or .catch() and let the original promise rejection go back to the caller.
Then, it is generally not recommended that you mix await with .catch() as the code is not as easy to follow. If you want to catch an exception from await, use try/catch around it. .catch() works, it's just not a preferred style if you're already awaiting the promise.
The one technical difference between these two styles:
async function someFunc()
let x = await fn().catch(err => {
console.log(err);
throw err;
});
// do something else with x
return something;
}
And, this:
async function someFunc()
let x;
try {
x = await fn();
} catch(err) {
console.log(err);
throw err;
}
// do something else with x
return something;
}
is if the function fn() you are calling throws synchronously (which it shouldn't by design, but could by accident), then the try/catch option will catch that synchronous exception too, but the .catch() will not. Because it's in an async function, the async function will catch the synchronous exception and turn it into a rejected promise for you automatically for the caller to see as a rejected promise, but it wouldn't get logged or handled in your .catch() handler.
One of the more beneficial cases for try/catch with await is when you have multiple await statements and you don't need to handle errors on any of them individually. Then, you can surround them with one try/catch and catch all the errors in one place.
async function someFunc(someFile) {
let fileHandle;
try {
// three await statements in a row, all using same try/catch
fileHandle = await fsp.open(someFile);
let data = await fsp.read(...);
// modify data
await fsp.write(...)
} catch(err) {
// just log and re-throw
console.log(err);
throw err;
} finally {
// close file handle
if (fileHandle) {
await fileHandle.close();
}
}
}
It depends.
Are you going to be calling multiple asynchronous functions that could error? You can wrap them all in a try/catch and perform common error handling without having to repeat yourself:
try {
result = await forSomeResult(param);
await forSomeOtherResult();
return await finalResult(result);
} catch { //catches all three at once!
throw new Error();
}
Do you only need to handle errors for this one, specific call? The .catch() pattern is fine. There is no race condition to worry about, await waits for the promise to resolve or reject, and this includes all success and failure callbacks attached to the promise. However, in your example, you're catching an error only to throw an empty one - in that case, it may be preferable to simply write this:
const result = await forSomeResult(param);
...and let the error propagate to the caller naturally.
I've seen a mixture of both styles used common enough that I think it's fine either way - they each have a particular strength.

How to save variable using await function and use callback when error occurs?

well I'm immigrating my code to ES7, syntaxes like arrow function and async/await. The problem is I don't get how to use call back when If I try to save variable and callback when error occurs at the same time using async/await.
Here's the code. I want to save result from request to redditJSON variable using await with rp but I want to add callback for error handling too. but I have no idea how to implement this.
rp is request-promise npm module.
let redditJSON = await rp(optForReddit)
EDIT 1
let redditJSON;
try { redditJSON = await rp(optForReddit) }
catch(err) { console.error(err) }
Create a common wrapper function, or install await-promise-wrapper:
As await exits, if error occurs there is no catch as we have in promise based implementation
//wrapper.js
export default function forActivity(promise){
return promise
.then( result => [null,result])
.catch(err => [err])
}
// yourFile.js
import forActivity from wrapper.js
const [err, redditJSON] = await forActivity(rp(optForReddit));
if(err) console.log("error");
Callbacks are dead in ES7 and should no longer be used. To perform error handling with the new async await pattern you should wrap the call in a try/catch block.
It should look something like this:
try {
let redditJSON = await rp(optForReddit);
// do some cool stuff
} catch(err) {
console.log(err);
// The contents of err should be what is thrown in the rp function
}
If you are using a library such as request-promise to perform this request the error should be thrown automatically and thrown up to this try/catch block.
However, remember you can always throw errors yourself as well, like so:
async myMethod() {
throw new Error('something went wrong');
}
Then catch it like this:
try {
await myMethod()
} catch (err) {
// err = 'Something went wrong'
}
You don't need to save off the temporary variable as it will never have a value if you make it to the catch block.
In the simple version that Paul posted, if you hit the catch block, reddittJSON will be undefined, so you can always check that if you need to.
Now the cool thing with the latest promises and await is that you can set many processes going asynchronously and then sync them up later.
try {
let promise1 = rp(longCall).promise();
let promise2 = rp(longCall2).promise();
let [response1, response2] = await Promise.all([promise1, promise2]);
} catch (error) {
console.log(error);
}
// response1 and response2 will be undefined here if there was an error
Read here for more information.
http://javascriptrambling.blogspot.com/2017/04/to-promised-land-with-asyncawait-and.html
After programming java for over 20 years, I'm really excited with the great leaps javascript has made in the last few years.

Why does babel translate async/await to regenerators?

All version of babel translate an await statement to a _asyncToGenerator call, it obviously has some shortcomings:
Code size grows dramatically
Requires the regeneratorRuntime library
From my understanding of the syntax I think any await should be equivalent to a Promise#then call, so the code below:
try {
let user = await getUser();
console.log(user.name);
}
catch (error) {
console.error(error);
}
is just equivalent to:
let promise$of$getUser$ = getUser();
$promise$of$getUser$.then(
$result$ => console.log($result$),
$error$ => console.error($error$)
);
In this way it is also possible to correctly map multiple await statements or even a mix of Promise#then and await statements to a Promise chain, so I must missed some cases where pure Promise#then is not suitable for await statements.
You can use the other 2 plugins: async-to-generator and async-to-module-method.
There's also an experimental plugin called kneden which does try to do what you are suggesting (async to promise). It's still WIP and doesn't account for all cases and most likely will not be able to.
I think you're overlooking loops:
for (let userId of userIds) {
const user = await getUser(userId);
console.log(user);
}

Categories

Resources