preface

Learning without thought is labor lost

Recently I have been reading some excellent NPM package code, the cause is that I feel that the code is not standard enough, not concise enough.

But I do not know what kind of code is a better code, after some thinking I think or to stand on the shoulders of giants.

Read good source code and learn how to write enjoyable code and then write a chapter summary to sort out the whole learning process and share it with others.

Why write so much at the beginning? Because I needed a reason to keep going. So I can ride the waves and move forward.

Without further ado, start summing up.

Evolution of JS asynchronous programming

Callback hell phase

Before we formally introduce await-to-js, let’s briefly review the evolution of asynchronous programming in JavaScript. Asynchronous programming was a big problem for front-end engineers before Promises came along, and their predecessors would have seen code like this a lot.


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

This code, which has callbacks nested in both vertical and horizontal callbacks, is also known as callback hell. You can see how disgusting it is, specifically with the following downsides

  • Difficult to maintain (don’t want to look at, but also maintain a **)
  • Difficult to catch errors (one at a time?)

All in all, this was a problem that needed to be addressed at the time, so in ES6, there were promises.

Promise phase

Promise is an elegant asynchronous programming solution. Syntactically, it is an object that represents the final completion or failure of an asynchronous operation. Semantics, it is a promise that promises you a result over time.

Since its prototype then,catch,finally returns a new promise, it allows us to make chain calls, solving the problem of traditional callback hell.

Because it has an all method, it can support multiple concurrent requests and retrieve data from concurrent requests.

With promises in place, the code above can be written as follows.

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

In contrast to the callback hell above, using promises helps us keep our code vertical only and provides callbacks to handle errors. Definitely a lot more elegant. But as good as promises are, there are still two things that fall short of each

  • Not synchronized enough (code still extends vertically)
  • You cannot error handle every asynchronous operation

This is why ES7 has async/await as the last solution to asynchronous programming.

async/await

Async functions are syntactic sugar for Generator functions. Async is represented with the keyword async and await is used inside the function. Compared to Generator, async functions are improved in the following four aspects:

  • Built-in actuator.GeneratorThe execution of a function must depend on the executor, andasyncThe function has its own executor, which is called in the same way as ordinary functions
  • Better semantics.asyncawaitIn contrast to*yieldMore semantic
  • Wider applicability.coModule convention,yieldA command can only be followed by a Thunk function or a Promise object. whileasyncFunction of theawaitThe command can be followed by a Promise or a value of primitive type (Number, string, Boolean, but this is equivalent to a synchronous operation)
  • The return value is Promise.asyncThe return value is a Promise object, which is more convenient than the Iterator returned by Generator functions and can be used directlythen()Method to call

Understand async/await

With async/await, the above code can be rewritten as follows

function async asyncTask(cb) {
  const asyncFuncARes = await asyncFuncA()
  const asyncFuncBRes = await asyncFuncB(asyncFuncARes)
  const asyncFuncCRes = await asyncFuncC(asyncFuncBRes)
}
Copy the code

We can also do error handling for each asynchronous operation

function async asyncTask(cb) { try { const asyncFuncARes = await asyncFuncA() } catch(error) { return new Error(error) }  try { const asyncFuncBRes = await asyncFuncB(asyncFuncARes) } catch(error) { return new Error(error) } try { const asyncFuncCRes = await asyncFuncC(asyncFuncBRes) } catch(error) { return new Error(error) } }Copy the code

Are the two shortcomings of each of the above promises optimized? Therefore, async/await is the last solution of ASYNCHRONOUS WRITING in JS. I personally think there is no problem, but I wonder if you have seen the above code. Is it not convenient or intelligent to use try/catch for error handling for every asynchronous operation?

Await-to-js – small and beautiful NPM package

Basic usage

This is how the author introduces the library

Async await wrapper for easy error handling without try-catch.

The Chinese translation is

Asynchronous wait wrappers that easily handle errors without a try-catch.

To make a simple comparison, here’s how we handled errors in asynchronous operations

function async asyncTask() {
    try {
      const asyncFuncARes = await asyncFuncA()
    } catch(error) {
      return new Error(error)
    }
    try {
      const asyncFuncBRes = await asyncFuncB(asyncFuncARes)
    } catch(error) {
      return new Error(error)
    }
    try {
      const asyncFuncCRes = await asyncFuncC(asyncFuncBRes)
    } catch(error) {
      return new Error(error)
    }
}
Copy the code

With await-to-js, we can handle errors like this

import to from './to.js';
function async asyncTask() {
   const [err, asyncFuncARes]  = await to(asyncFuncA())
   if(err) throw new (error);
   
   const [err, asyncFuncBRes]  = await tp(asyncFuncB(asyncFuncARes))
   if(err) throw new (error);
   
   const [err, asyncFuncCRes]  = await to(asyncFuncC(asyncFuncBRes)
   if(err) throw new (error);
}

Copy the code

Isn’t it much simpler?

What dark magic did the author use?

It may surprise you, but the source code is only 15 lines long.

Source code analysis

export function to<T, U = Error> ( promise: Promise<T>, errorExt? : object ): Promise<[U, undefined] | [null, T]> { return promise .then<[null, T]>((data: T) => [null, data]) .catch<[U, undefined]>((err: U) => { if (errorExt) { const parsedError = Object.assign({}, err, errorExt); return [parsedError, undefined]; } return [err, undefined]; }); } export default to;Copy the code

Above here is TS version of the source, but considering that some students may not have contact with TS, I focus on the following version of the JS version of the source.

export function to(promise, errorExt) {
    return promise
        .then((data) => [null, data])
        .catch((err) => {
        if (errorExt) {
            const parsedError = Object.assign({}, err, errorExt);
            return [parsedError, undefined];
        }
        return [err, undefined];
    });
}
export default to;
Copy the code

Let’s get rid of the errorExt custom error text here. The core code looks like this

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

As you can see, the logic of the code in Chinese looks like this

  • Either success or failure returns an array, the first item of which is associated with the error, and the second item of which is associated with the outcome
  • On success, the first item of the array, the error message, is empty, and the second item of the array, the response, is returned normally
  • If it fails, the first item in the array, which is the error message, is the error message, and the second item in the array, which is the response result, returns undefined

After the above analysis we can determine that there is no black magic in the world, there is no you can not do, only you can not think.

Assign (errorExt); assign (errorExt); assign (errorExt); assign (errorExt); assign (errorExt)

conclusion

Source code is not terrible, terrible is their fear in the face of the unknown.

Dare to face, dare to try, to the next level.

Keep it up, kid.

Follow me, VX: Codebangbang, Nuggets: Love hei hei.

The resources

  • Warehouse address: github.com/scopsy/awai…
  • How to write async await without try-catch blocks in Javascript