Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async await usage and error handling woes

Both try's print Promise { <pending> } and the second one has an Unhandled Promise Rejection Warning. I've had success just using Promises with .then and .catch, but there's some stuff I'd like to code in more of a synchronous way using async/await. Should I be using Yield instead perhaps?

try {
  var tokens = getNewTokens('refresh-token', 1)
  console.log(tokens)
} catch (error) {
  console.log(error.message)
}

try {
  tokens = getNewTokens('no-such-refresh-token', 1)
  console.log(tokens)
} catch (error) {
  console.log(error.message)
}

function getRefreshToken (refreshToken, userId) {
  return new Promise((resolve, reject) => {
    if (refreshToken === 'refresh-token' && userId === 1) {
      return resolve({
        refreshToken: 'refresh-token',
        accessToken: 'jwt'
      })
    } else {
      const error = Error('Refresh token not found')
      return reject(error)
    }
  })
}

async function getNewTokens (refreshToken, userId) {
  if (!refreshToken || !userId) {
    throw Error('Missing params')
  }
  try {
    var result = await getRefreshToken(refreshToken, userId)
  } catch (error) {
    throw Error(error.message)
  }
  if (!result) {
    throw Error('Something went bonk')
  }
  // Do stuff..
  // get user from DB
  // update refresh token with a new one
  // make new jwt
  return {
    user: {
      id: 1,
      name: 'Jerry',
      role: 'admin'
    },
    newRefreshToken: 'new-refresh-token',
    newAccessToken: 'new-jwt'
  }
}
like image 976
Jerry Botticelli Avatar asked Jan 23 '17 18:01

Jerry Botticelli


3 Answers

Using async and await does not make your whole program asynchronous. It makes particular functions asynchronous. In the end, async is syntactical sugar that makes writing promise code easier.

An async function returns a Promise. The code that calls the asynchronous function must treat it as a function that returns a promise. (The only exception is when the code that is calling the async function is itself in an async function, in which case it can be awaited.)

So throwing an Error gets translated into a Promise rejection. You can't catch it in a try..catch block for the simple reason that the try..catch is over way before the error is thrown! (Again, the exception is when you're calling it from an async function. If you are awaiting an asynchronous function, a try..catch block will work. In this case the try..catch block is treated as if it were adding a Promise#catch function.)

You ultimately have to catch errors from an async function using the normal Promise#catch method or with the second argument to Promise#then:

getNewTokens('no-such-refresh-token', 1)
    .then(function(tokens) {
        console.log(tokens);
    }, function(error) {
        console.log(error.message);
    });
like image 139
lonesomeday Avatar answered Nov 15 '22 21:11

lonesomeday


I would like to submit this as a reasonable and readable way to handle errors using async/await.

const value = await asyncFunction().catch(err => new Error(err))
if (value instanceof Error) return res.status(500).send('There was an error doing something')

By adding the .catch() method, which returns an Error, thus assigning it to the value variable, we can be sure that some value will be present in our variable. The only additional line of code necessary is the if statement to test if the value is an instance of Error. If it was caught for any reason, it will definitely be an instanceof Error (our code makes sure of that), and our program can safely handle that scenario (in the above case we are returning 500 as a server response, thus preventing the program from executing any dangerous code as a result of the error in the asyncFunction).

I intentionally wrote this in 2 lines to highlight the fact that this can be written tersely and avoid the use of try/catch blocks, which many do not enjoy reading (myself being one).

If you're cool with reading try/catch blocks, congratulations. I just find it makes the code look janky. Oh well. ¯\_(ツ)_/¯

like image 25
Ben Steward Avatar answered Nov 15 '22 19:11

Ben Steward


A practical answer for beginners in async/await and error handling

You cannot use await syntax outside of a function that is not declared with async or is a promise.

async function myAsyncFunction() {
  // code
}

or

const myPromiseFunction = () => new Promise((resolve, reject) => {
    // code
})

So ultimately, you have to use the old Promise way to consume a function declared as async

myAsyncFunction().then(() => console.log('here'));

because both are Promises.

Now to actually handle errors like you are used to in the then-catch way, you have to construct your async function like so...

async function getUser(username) {
  return await db.users.get({ username });
}

then you can use this like a normal Promise outside of an async method like so...

getUser()
.then(user => console.log(user))
.catch(err => console.error(err));

or you guessed it, use it within an async function using await.

async function getJoffery() {
  return await getUser('joffery');
}
like image 7
King Friday Avatar answered Nov 15 '22 21:11

King Friday