Catching Without Awaiting sentence

It is more cumbersome to use async/await when performing a task that needs to wait some time to return. If the async method does not return a value, we will not catch the exception.

In my last article, Learn to Throw Again, I wrote about how to catch errors thrown by both callback functions and throws when using async/await. In this article, we will discuss how to perform asynchronous operations and catch exceptions in the “background” (double quotes here, because there is no real background operation on a single-line platform)

Starting with the pattern of callback functions, consider the following code:

function email(user, message, callback) {
  if(! user) {// Throw an exception
    throw new Error('Invlid user');
  }
  if(! user.address) {// The callback function may throw an exception
    return callback();
  }
  / / asynchronous
  return mailer.send(user.address, message, callback);
}
Copy the code

The above code follows the typical throw-on-bad-input/callback-ados-errors pattern (an exception is thrown asynchronously whenever the program receives incorrect input). If we want to send an email, we call it like this:

email(user, message, () => {});
Copy the code

Calling this function may still throw an exception for invalid input. However, if an error occurs in the transmission of the E-mail, this function call ignores the error thrown asynchronously.

Let’s change this to the Promise version:

function email(user, message) {
  if(! user) {throw new Error('Invlid user');
  }
  if(! user.address) {return Promise.resolve();
  }
  return mailer.send(user.address, message); The function returns a Promise
}
Copy the code

In this way, exceptions can still be caught for invalid input. For the mailer.send() operation, which returns a Promise, we can easily catch the exception with promise.catch () :

email(user, message).catch((a)= > {});
Copy the code

Both callbacks and promises are asynchronous, and our application will not be blocked by email.

For async/await mode, if in try… The catch statement does not use the await keyword, so try… The catch clause doesn’t really work. Take a look at the following version of Async:

function email(user, message) {
  if(! user) {throw new Error('Invlid user');
  }
  if(! user.address) {return;
  }
  return mailer.send(user.address, message); // async function
}
Copy the code

If we call this:

try {
  email(user, message);
} catch (err) {
  Bounce.rethrow(err, 'system');
}
Copy the code

For illegal input errors, exceptions will still be thrown normally, which is fine. But any exceptions returned asynchronously, such as those thrown in Mailer.send (), are ignored. Whether we want to or not, we can’t catch such errors. To fix this bug, use the await keyword. But the problem is that this will cause the whole “background operation” to block.

One solution is to mix async/await and Promise:

email(user, message).catch((a)= > {});
Copy the code

The problem, however, is that for users without address, the return type of this method is not a Promise, so there is no catch() method, so TypeError will appear: Cannot read property ‘catch’ of undefined

You might try to declare the email() function as async and make it return a Promise, but that’s not a good solution because async/await is really just a wrapper around the Promise object. It is completely unnecessary to declare a function async without the await keyword. Because async always returns a Promise and gets results via next-tick, this wastes the performance cost of Promise wrapping and next-tick event loops.

In addition, if you want to use async functions in a loop and a lot of tasks are being performed in the loop, but a lot of tasks are not really asynchronous, there is no need to use async/await. Checking if You really need to await can be performed by checking if you really need to await in hapi.js:

var response = (typeof func === 'function' ? func(this) : this._invoke(func));
if (response && typeof response.then === 'function') { // Skip await if no reason to
  response = await response;
}
Copy the code

To determine whether we really need await is to determine whether there is a then method and that the then method is a function. Because the purpose of await is to get the return result of an asynchronous operation.

If you can guarantee that the email method always returns a Promise, we could do this by changing our email() function, but that would be a quick fix! The code is clunky and uses unnecessary asynchronous operations. In a full async/await function call stack, there is no need for us to manually build promises. This is fine for this example, but more importantly, we can’t always do this by changing the email() method, because this is just an example. In practice, the email() method might have been introduced via the module.

One solution is to call async functions with the await keyword. Normally, using a blocking operation in a function does not throw an exception if it does not wait for the function to complete, but we can try… Catch to wrap:

async function backgroundEmail(user, message) {
  try {
    await email(user, message);
  } catch (err) {
    Bounce.rethrow(err, 'system'); }}Copy the code

Then call backgroundEmail without await:

backgroundEmail(user, message);
Copy the code

This allows us to catch not only application exceptions, but also exceptions thrown asynchronously.

To make exception catching easier, we use the Bounce module, which provides a background() method.

Bounce.background((a)= > email(user, message));
Copy the code

If we use node.js’s AssertionError prototype, we can cause Bounce to throw an input exception.

Async /await functions remove some of the synchronous functions (() => {}) and we have to write extra code to achieve the same effect as normal functions. But this limitation can be easily overcome with a new tool library.