Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly implement error handling in async/await case

I use async/await ecma6 standard without any custom library.

I don't get at the moment how I can properly catch and throw errors. I have multiple async/await functions and if somewhere down below an critical error happens I want to throw the error to the top and of all async functions and stop execution of the function.

I tried to throw exceptions out of the async/await function and catch it in the target function but I get a error in node.js:

    this.basicAuthLogin= async function(user)
{
    "use strict";
    const login = new Login(this.host, this.url, user, user.pw);

    //getSessionID throws error
    this.sessionID = getSessionID(result.request.response);
}

(node:13964) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: getSessionID response is undefined (node:13964) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. Debugger attached.

So it seems I am not allowed to throw exceptions out of async functions or even rethrow it in the catch block of the promise in node.js?

So how do I get this working? Am I supposed to catch the error in the async function and return the error in the promise and rethrow then out of the async function?

   this.basicAuthLogin= async function(user)
{
    "use strict";
    const login = new Login(this.host, this.url, user, user.pw);
   try{
    //getSessionID throws error
    this.sessionID = getSessionID(result.request.response);
   } catch(err) { return err;}
}

But this would mean that in my call stack from the first async function every function needs be async and I have to await the promise even though I don't really need it.

I hope somebody can enlighten me.

Regards Ruvi

Edit basic call stack pseudo code:

   async startTest[arr]{

    for (var i = 0; i < arr.length; i++)
    {
      try {
          await runStep(arr[i];
        } catch(err) { 
            console.log(err);
            break; 
        }
      }
    }

  async runStep(step)
  {
     try {
     var userIsValid = await validateUser(step.user);
     var req = buildRequest(step.request);
     var result = await sendRequest(req);
     var verify = verifyResult();
     } catch(err){ throw err;}
  }

  async validateUser(user)
  {
     //make checks
     //
     var result = await this.authenticate(parameter).catch(err => {throw err});
     userFound = true;
   }

  function authenticate(parameter) {
  //can throw async function
   basicAuthLogin(parameter).catch(err => {throw err};

   }

  function async basicAuthLogin(parameter()
  {
   try {
    //can throw  async function
      var result = await request(parameter);
      //can throw sync function
      this.sessionID = getSessionID(response);
      //can throw   sync function
      } catch(err) { throw err; }
   }
like image 613
Ruvi Avatar asked Feb 08 '18 12:02

Ruvi


1 Answers

One of the great things about async/await is that they enable try/catch to work with your asynchronous code.

Your first basicAuthLogin function is absolutely fine (provided getSessionID is a synchronous function; if it isn't, you're missing an await [you've now said it is]). The code using basicAuthLogin must handle the possibility it will throw (either by handling the error or allowing it to propagate to its caller, which is responsible for handling it). So either:

// In an `async` function
try {
    await this.basicAuthLogin(/*...*/);
} catch (e) {
    // Handle the fact something failed
}

or

// NOT in an `async` function:
this.basicAuthLogin(/*...*/)
    .catch(e => { /* Handle the fact something failed */ });

If the code using it does one of those two things (or lets the error propagate to code that does one of those two things), you won't get the "Unhandled rejection" error.

In response to my comment asking if getSessionID was asynchronous, you wrote:

No it is not async it is a simple function that throws an exception that I want to catch 5 or 6 floors up the call stack but it seems I am not allowed to do that.

Here's a live example of doing that (in my case, I've made basicAuthLogin actually use something asynchronous prior to getSessionID, but it doesn't really matter):

const getSessionID = () => {
  throw new Error("Failed");
};
const somethingAsync = () => new Promise(resolve => {
  setTimeout(resolve, 100);
});
const basicAuthLogin = async function(user)
{
    "use strict";
    await somethingAsync();
    /*
    const login = new Login(this.host, this.url, user, user.pw);
    */

    //getSessionID throws error
    getSessionID();
};

const wrapper1 = async () => {
  await basicAuthLogin();
};
const wrapper2 = async () => {
  await wrapper1();
};
const wrapper3 = async () => {
  await wrapper2();
};

// Top-level caller
(async () => {
  try {
    await wrapper3();
  } catch (e) {
    console.log("Caught error: " + e.message);
  }
})();

The rule is just like it is with exceptions (because notionally these are exceptions):

  1. Either handle it (e.g., try/catch), or let it propagate to the caller (typically by not doing anything at all), and

  2. The top level MUST handle it

That Rule #2 means that when you transition from non-async code to async code (typically right at the top of your stack), you need a wrapper. Either:

(async () => {
  try {
    await theFirstAsyncFunction();
    await theNextAsyncFunction();
    await aThirdAsyncFunction();
  } catch (e) {
    // handle the error
  }
})();

or

(async () => {
  await theFirstAsyncFunction();
  await theNextAsyncFunction();
  await aThirdAsyncFunction();
})().catch(e => { /* handle the error */});

or of course:

theFirstAsyncFunction()
.then(() => theNextAsyncFunction())
.then(() => aThirdAsyncFunction())
.catch(e => { /* handle the error */});

The common denominator there is: The top level always handles the error.

like image 186
T.J. Crowder Avatar answered Oct 10 '22 06:10

T.J. Crowder