try/catch blocks with async/await
Asked Answered
A

10

234

I'm digging into the node 7 async/await feature and keep stumbling across code like this

function getQuote() {
  let quote = "Lorem ipsum dolor sit amet, consectetur adipiscing elit laborum.";
  return quote;
}

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

main();

This seems to be the only possibility resolve/reject or return/throw with async/await, however, v8 doesn't optimise code within try/catch blocks?!

Are there alternatives?

Approval answered 30/11, 2016 at 9:6 Comment(6)
What does 'throw after an await isn't successful' mean? If it errors? If it does not return the expected result? You could rethrow in the catch block.Lindseylindsley
afaik v8 do optimize try/catch, a throw statement is the slow oneMeridethmeridian
I still dont understand the question. You van use old promise chaining, but I dont think it would be faster. So you are concerned about the performance of try-catch? Then what is it to do with async await?Meridethmeridian
Check my answer I tried to get a cleaner approachTolliver
Here you can do this https://mcmap.net/q/75805/-get-data-using-await-async-without-try-catch It looks cleanerFeodor
See also syntax to handle rejections with async/awaitSchacker
H
268

Alternatives

An alternative to this:

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

would be something like this, using promises explicitly:

function main() {
  getQuote().then((quote) => {
    console.log(quote);
  }).catch((error) => {
    console.error(error);
  });
}

or something like this, using continuation passing style:

function main() {
  getQuote((error, quote) => {
    if (error) {
      console.error(error);
    } else {
      console.log(quote);
    }
  });
}

Original example

What your original code does is suspend the execution and wait for the promise returned by getQuote() to settle. It then continues the execution and writes the returned value to var quote and then prints it if the promise was resolved, or throws an exception and runs the catch block that prints the error if the promise was rejected.

You can do the same thing using the Promise API directly like in the second example.

Performance

Now, for the performance. Let's test it!

I just wrote this code - f1() gives 1 as a return value, f2() throws 1 as an exception:

function f1() {
  return 1;
}

function f2() {
  throw 1;
}

Now let's call the same code million times, first with f1():

var sum = 0;
for (var i = 0; i < 1e6; i++) {
  try {
    sum += f1();
  } catch (e) {
    sum += e;
  }
}
console.log(sum);

And then let's change f1() to f2():

var sum = 0;
for (var i = 0; i < 1e6; i++) {
  try {
    sum += f2();
  } catch (e) {
    sum += e;
  }
}
console.log(sum);

This is the result I got for f1:

$ time node throw-test.js 
1000000

real    0m0.073s
user    0m0.070s
sys     0m0.004s

This is what I got for f2:

$ time node throw-test.js 
1000000

real    0m0.632s
user    0m0.629s
sys     0m0.004s

It seems that you can do something like 2 million throws a second in one single-threaded process. If you're doing more than that then you may need to worry about it.

Summary

I wouldn't worry about things like that in Node. If things like that get used a lot then it will get optimized eventually by the V8 or SpiderMonkey or Chakra teams and everyone will follow - it's not like it's not optimized as a principle, it's just not a problem.

Even if it isn't optimized then I'd still argue that if you're maxing out your CPU in Node then you should probably write your number crunching in C - that's what the native addons are for, among other things. Or maybe things like node.native would be better suited for the job than Node.js.

I'm wondering what would be a use case that needs throwing so many exceptions. Usually throwing an exception instead of returning a value is, well, an exception.

Hallucinosis answered 30/11, 2016 at 11:4 Comment(3)
I know the code can easily be written with Promises, as mentioned, I've seen it around on various examples, that's why I'm asking. Having a single operation within try/catch might not be an issue, but multiple async/await functions with further application logic might be.Approval
@Approval "might be" and "will be" is a difference between speculation and actually testing. I tested it for a single statement because that is what was in your question but you can easily convert my examples to test for multiple statements. I also provided several other options to write asynchronous code that you also asked about. If it answers your question then you may consider accepting the answer. To sum it up: of course exceptions are slower than returns but their usage should be an exception.Hallucinosis
Throwing an exception is indeed supposed to be an exception. That being said, the code is unoptimised whether you throw an exception or not. The performance hit comes from using try catch, not from throwing an exception. While the numbers are small, it's almost 10 times slower according to your tests, which is not insignificant.Trochanter
M
43

Alternative Similar To Error Handling In Golang

Because async/await uses promises under the hood, you can write a little utility function like this:

export function catchEm(promise) {
  return promise.then(data => [null, data])
    .catch(err => [err]);
}

Then import it whenever you need to catch some errors, and wrap your async function which returns a promise with it.

import catchEm from 'utility';

async performAsyncWork() {
  const [err, data] = await catchEm(asyncFunction(arg1, arg2));
  if (err) {
    // handle errors
  } else {
    // use data
  }
}
Mclin answered 16/3, 2018 at 1:37 Comment(5)
I created an NPM package that does exactly the above - npmjs.com/package/@simmo/taskMarv
@Marv You might be re-inventing the wheel - there's already a popular package that does exactly this: npmjs.com/package/await-to-jsSoler
golang is not node.Jolly
Ah welcome to StackOverflow where 4 years after the question is asked a response such as, golang is not node floats in. I think the point is you can write a utility function in Node to do what he is asking. It may be in Go but the point is clear.Alvie
@DylanWright The answer isn't even written in Go—it's JavaScript. It's just stating that this is how you'd achieve async logic that's similar to how Go does it.Knurl
B
32

An alternative to try-catch block is await-to-js lib. I often use it. For example:

import to from 'await-to-js';

async function main(callback) {
    const [err,quote] = await to(getQuote());
    
    if(err || !quote) return callback(new Error('No Quote found'));

    callback(null,quote);

}

This syntax is much cleaner when compared to try-catch.

Banderilla answered 15/1, 2018 at 12:39 Comment(4)
Tried this and loved it. Clean, and readable code at the expense of installing a new module. But if you are planning to write a lot of async functions, I gotta say this is a great addition! ThanksDissuade
You dont even need to install library. If you look at the source code for it, its literally 1 function. Just copy and paste that function into a utility file in your project and your good to go.Bridlewise
Here is a one-liner for the to function: const to = promise => promise.then(res => [null, res]).catch(err => [err || true, null]);.Celibate
To iqbal125, using a library avoids you to bear the responsibility of the docs, the tests and Typescript support (as long as the library is tested, documented and supports ts)Unconventional
B
24
async function main() {
  var getQuoteError
  var quote = await getQuote().catch(err => { getQuoteError = err }

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

Alternatively instead of declaring a possible var to hold an error at the top you can do

if (quote instanceof Error) {
  // ...
}

Though that won't work if something like a TypeError or Reference error is thrown. You can ensure it is a regular error though with

async function main() {
  var quote = await getQuote().catch(err => {
    console.error(err)      

    return new Error('Error getting quote')
  })

  if (quote instanceOf Error) return quote // get out of here or do whatever

  console.log(quote)
}

My preference for this is wrapping everything in a big try-catch block where there's multiple promises being created can make it cumbersome to handle the error specifically to the promise that created it. With the alternative being multiple try-catch blocks which I find equally cumbersome

Bane answered 30/1, 2018 at 17:37 Comment(0)
T
21

A cleaner alternative would be the following:

Due to the fact that every async function is technically a promise

You can add catches to functions when calling them with await

async function a(){
    let error;

    // log the error on the parent
    await b().catch((err)=>console.log('b.failed'))

    // change an error variable
    await c().catch((err)=>{error=true; console.log(err)})

    // return whatever you want
    return error ? d() : null;
}
a().catch(()=>console.log('main program failed'))

No need for try catch, as all promises errors are handled, and you have no code errors, you can omit that in the parent!!

Lets say you are working with mongodb, if there is an error you might prefer to handle it in the function calling it than making wrappers, or using try catches.

Tolliver answered 13/6, 2019 at 12:50 Comment(4)
You have 3 functions. One getting values and catching the error, another one you return if there is no error and finally a call to the first function with a callback to check if that one returned an error. All this is solved by a single "promise".then(cb).catch(cb) or trycatch block.Lerma
@Chiefkoshi As you can see a single catch wouldn't do as the error is being handled different in all three cases. If the first one fails it does return d(), if the second one fails it returns null if the last one fails a different error message is shown. The question asks for handling errors when using await. So that is the answer also. All should execute if any one fails. Try catch blocks would require three of them in this particular example which is not cleanerTolliver
The question does not ask for executing after failed promises. Here you wait for B, then run C and return D if they errored. How is this cleaner? C has to wait for B yet they are independent of each other. I dont see a reason for why they would be in A together if they are independent. If they were dependent on each other you'd want to stop execution of C if B fails, the job of .then.catch or try-catch. I assume they return nothing and perform some asynchronous actions completely unrelated to A. Why are they called with async await?Lerma
The question is pertaining alternatives to try catch blocks to handle errors when using async/await. The example here is to be descriptive and is nothing but an example. It shows individual handling of independent operations in a sequential way which is usually how async/await are used. Why are they called with async await, is just to show how it could be handled. Its descriptive more than justified.Tolliver
O
5

I think, a simple and well explained example is from How to use promises of MDN DOCS.

As an example they use the API Fetch then 2 types, one normal and the other an hybrid where the async and Promise are mixed together.

  1. Simple Example
async function myFetch() {
  let response = await fetch("coffee.jpg");
  // Added manually a validation and throws an error
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  let myBlob = await response.blob();

  let objectURL = URL.createObjectURL(myBlob);
  let image = document.createElement("img");
  image.src = objectURL;
  document.body.appendChild(image);
}

myFetch().catch((e) => {
  // Catches the errors...
  console.log("There has been a problem with your fetch operation: " + e.message);
});

  1. Hybrid approach

Since an async keyword turns a function into a promise, you could refactor your code to use a hybrid approach of promises and await, bringing the second half of the function out into a new block to make it more flexible:

async function myFetch() {
  // Uses async
  let response = await fetch("coffee.jpg");
  // Added manually a validation and throws an error
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return await response.blob();
}

myFetch()
  .then((blob) => {
    // uses plain promise
    let objectURL = URL.createObjectURL(blob);
    let image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  })
  .catch((e) => console.log(e));

Adding error handling

  1. Normal
async function myFetch() {
  try {
    let response = await fetch("coffee.jpg");

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    let myBlob = await response.blob();
    let objectURL = URL.createObjectURL(myBlob);
    let image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  } catch (e) {
    console.log(e);
  }
}

myFetch();

  1. Hybrid (Best)
async function myFetch() {
  let response = await fetch("coffee.jpg");
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return await response.blob();
}

myFetch()
  .then((blob) => {
    let objectURL = URL.createObjectURL(blob);
    let image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  })
  .catch(
    (
      e // Not need a try catch. This will catch it all already!
    ) => console.log(e)
  );

Best solution

The best solution given, which follow these principle but adds more clarity is this answer --> StackOverflow: try/catch blocks with async/await I believe. Here

function promiseHandle(promise) {
  return promise.then((data) => [null, data]).catch((err) => [err]);
}

async function asyncFunc(param1, param2) {
  const [err, data] = await promiseHandle(expensiveFunction(param1, param2));
  // This just to show, that in this way we can control what is going on..
  if (err || !data) {
    if (err) return Promise.reject(`Error but not data..`);
    return Promise.reject(`Error but not data..`);
  }
  return Promise.resolve(data);
}

Operative answered 11/12, 2021 at 20:15 Comment(1)
I think you forgot, in the last code block, the last Promise.reject would reject with data?Jacquez
C
3

No need for a library like await-to-js, a simple one-liner for the to-function (also shown in other answers) will do:

const to = promise => promise.then(res => [null, res]).catch(err => [err || true, null]);

Usage:

async function main()
{
    var [err, quote] = await to(getQuote());
    if(err)
    {
        console.log('warn: Could not get quote.');
    }
    else
    {
        console.log(quote);
    }
}

However, if the error leads to termination of the function or program, such as:

async function main()
{
    var [err, quote] = await to(getQuote());
    if(err) return console.error(err);
    console.log(quote);
}

Then you might as well simply let the error return from main() automatically, which is the intended purpose of an exception anyway:

async function main()
{
    var quote = await getQuote();
    console.log(quote);
}

main().catch(err => console.error('error in main():', err));

Throwing an error vs returning an error

If you are expected to deal with an error that is expected to occur, then using throw or reject is bad practice. Instead, let the getQuote() function always resolve using any of these:

  • resolve([err, result])
  • resolve(null)
  • resolve(new Error(...))
  • resolve({error: new Error(), result: null})
  • etc.

Throwing an error (or the equivalent in async: rejecting a promise) must remain an exception. Since an exception only happens when things go south, and should not happen during normal usage, optimization is therefore not a priority. Thus, the only consequence of an exception, can be termination of the function, which is the default behavior if not caught anyway.

Unless you deal with badly designed 3rd-party libraries, or you are using a 3rd-party library function for an unintended use-case, you should probably not be using the to-function.

Celibate answered 14/11, 2021 at 12:52 Comment(0)
L
2

I'd like to do this way :)

const sthError = () => Promise.reject('sth error');

const test = opts => {
  return (async () => {

    // do sth
    await sthError();
    return 'ok';

  })().catch(err => {
    console.error(err); // error will be catched there 
  });
};

test().then(ret => {
  console.log(ret);
});

It's similar to handling error with co

const test = opts => {
  return co(function*() {

    // do sth
    yield sthError();
    return 'ok';

  }).catch(err => {
    console.error(err);
  });
};
Leveloff answered 12/3, 2018 at 9:1 Comment(2)
Code isn't very clear man, looks interesting though, could you edit?Tolliver
It is unfortunate that there is no explanation in this answer because it actually does demonstrate a great way to avoid try-catch'ing every const you assign with await!Benzo
S
1

catching in this fashion, in my experience, is dangerous. Any error thrown in the entire stack will be caught, not just an error from this promise (which is probably not what you want).

The second argument to a promise is already a rejection/failure callback. It's better and safer to use that instead.

Here's a typescript typesafe one-liner I wrote to handle this:

function wait<R, E>(promise: Promise<R>): [R | null, E | null] {
  return (promise.then((data: R) => [data, null], (err: E) => [null, err]) as any) as [R, E];
}

// Usage
const [currUser, currUserError] = await wait<GetCurrentUser_user, GetCurrentUser_errors>(
  apiClient.getCurrentUser()
);
Spatola answered 26/6, 2019 at 7:30 Comment(0)
O
1

In case of Express framework, I generally follow the following method. We can create a function which resolves a promise. Like the catchAsync function:

const catchAsync = (fn) => (req, res, next) =>{
    Promise.resolve(fn(req, res, next)).catch((err) => next(err));
});

This function can be called wherever we require try/catch.It takes in the function which we call and resolves or rejects it based on the action being carried. Here's how we can call it

const sampleFunction = catchAsync(async (req, res) => {
           const awaitedResponse = await getResponse();
           res.send(awaitedResponse);
});
Overrate answered 17/11, 2021 at 9:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.