Blog.grossman.io /how-to-writ…

In ES7, we can write asynchronous functions using async & await, which makes our asynchronous functions look like synchronized code.

In previous versions (ES6), we could use Promise to simplify our asynchronous programming process and avoid callback hell.

The callback hell

Callback hell is a semantically generated term that can be explained by the following:

function AsyncTask() {
   asyncFuncA(function(err, resultA){
      if(err) return cb(err);

      asyncFuncB(function(err, resultB){
         if(err) return cb(err);

          asyncFuncC(function(err, resultC){
               if(err) return cb(err);

               // And so it goes....
          });
      });
   });
}
Copy the code

In the example above, the constant callbacks make it very difficult to maintain and manage the control process. Consider a case where an if statement needs to execute some other method, and the result of the FunctionA callback is foo.

Use Promise optimizations

With THE advent of ES6 and Promise, we can simplify the previous “callback hell” code as follows:

function asyncTask(cb) {
   asyncFuncA.then(AsyncFuncB)
      .then(AsyncFuncC)
      .then(AsyncFuncD)
      .then(data= > cb(null, data)
      .catch(err= > cb(err));
}
Copy the code

Does it look more comfortable to write this way?

However, in a real business scenario, the processing of asynchronous flows can be a little more complicated. For example,

If you’re in one of your (Node.js) servers, you might want to:

  1. Save data 1 to the database (Step 1)
  2. Find another data 2 based on saved data 1 (Step 2)
  3. If data 2 is found, perform some other asynchronous tasks (other tasks)
  4. After all the tasks have been completed, you may need to use the results you obtained in step 1 to give feedback to the user.
  5. If an error occurs during the execution of a task, you need to tell the user at which step the error occurred.

With the Promise syntax, this certainly looks more concise, but it still seems a little confusing to me.

ES7 Async/await

You need to use a translator to use Async/Await, and you can use the Babel plug-in or Typescript to add the required tools.

If you use async/await at this point, you will find the code much more comfortable to write. It allows us to write code like this:

async function asyncTask(cb) {
    const user = await UserModel.findById(1);
    if(! user)return cb('No user found');
    const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
    
    if(user.notificationsEnabled) {
         await NotificationService.sendNotification(user.id, 'Task Created');  
    }
    
    if(savedTask.assignedUser.id ! == user.id) {await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
    }
    
    cb(null, savedTask);
}
Copy the code

The above code looks a lot more readable, but how do you handle errors?

Exception handling

When using promises for asynchronous tasks, some errors can occur such as database connection errors, database model validation errors, and so on.

When an asynchronous function is waiting for a Promise to return a value, it throws an exception if the Promise method reports an error. This exception can be caught in the catch method.

When using Async/Await, we usually use try/catch statements for exception catching.

try{
    //do something
}
catch{
   // deal err
}
Copy the code

I have no background in writing strongly typed languages, so adding extra try/catch statements adds extra code to me, which I think is very redundant and dirty. I’m sure it’s probably personal preference, but that’s how I look at it.

So the previous code looks like this:

async function asyncTask(cb) {
    try {
       const user = await UserModel.findById(1);
       if(! user)return cb('No user found');
    } catch(e) {
        return cb('Unexpected error occurred');
    }

    try {
       const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
    } catch(e) {
        return cb('Error occurred while saving task');
    }

    if(user.notificationsEnabled) {
        try {
            await NotificationService.sendNotification(user.id, 'Task Created');  
        } catch(e) {
            return cb('Error while sending notification'); }}if(savedTask.assignedUser.id ! == user.id) {try {
            await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
        } catch(e) {
            return cb('Error while sending notification');
        }
    }

    cb(null, savedTask);
}
Copy the code

The other way to

Recently I’ve been using Go-lang for coding and really like their solution, which looks something like this:

data, err := db.Query("SELECT ...")
iferr ! =nil { return err }
Copy the code

I think it’s cleaner and has less code than using try/catch blocks, which makes it more readable and maintainable.

With Await, however, if no try-catch is provided to handle the exception, the program will silently exit when an error occurs (you see an exception that is not thrown). If you do not provide a catch statement to catch the error, you will have no control over it.

When I sat down with Tomer Barnea(my good friend) and tried to find a simpler solution, we got the next usage: Remember: Await returns a Promise

Async & await exception catch utility function

Armed with this knowledge, we can make a small general purpose function to help us catch these errors.

// to.js
export default function to(promise) {
   return promise.then(data= > {
      return [null, data];
   })
   .catch(err= > [err]);
}
Copy the code

This generic function receives a Promise, returns the successful values as an array of added value, and receives the first caught error in the catch method.

import to from './to.js';

async function asyncTask(cb) {
     let err, user, savedTask;

     [err, user] = await to(UserModel.findById(1));
     if(! user)return cb('No user found');

     [err, savedTask] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
     if(err) return cb('Error occurred while saving task');

    if(user.notificationsEnabled) {
       const [err] = await to(NotificationService.sendNotification(user.id, 'Task Created'));  
       if(err) return cb('Error while sending notification');
    }

    cb(null, savedTask);
}
Copy the code

The above example is just a simple use case using this solution. You can add an interception method (debugging-like breakpoint) to io.js that will receive the original error object, print a log, or do whatever else you want, and then return the object after the operation.

We created a simple NPM package (Github Repo) for this library, which you can install using the following methods:

npm i await-to-js
Copy the code

This article is just a different way of looking at Async/Await functionality, entirely based on personal opinion. You can achieve similar results using promises, try-catch alone, and many other solutions. As long as you like it and it works.

Cause to think

Async /await can be used in conjunction with a promise, that is, a catch at the end of an asynchronous process control with a promise.

async function task() {return await req();
}

task().catch(e => console.error(e))
Copy the code

When used with async/await in conjunction with promise. all, how to catch exceptions & handle them?

async function hello(flag){
    return new Promise((resolve, reject) => {
        if(flag) setTimeout(() => resolve('hello'), 100);
        else reject('hello-error');
    })
}

async function demo(flag){
    return new Promise((resolve, reject) => {
        if(flag) setTimeout(() => resolve('demo'), 100);
        else reject('demo-error');
    })
}

async function main() {let res = await hello(1).catch(e => console.error(e));
    console.log('res => ', res);
    let result = await Promise.all([hello(1), demo(1)]);
    // let result = await Promise.all([hello(1), demo(0)]).catch(e => console.error('error => ', e));
    console.log('result => ', result);
}

main()
Copy the code