try/catch blocks with async/await

半腔热情 提交于 2019-11-26 12:55:43

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.

Pulkit chadha

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.

Tony
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

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
  }
}

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.

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);
  });
};

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()
);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!