preface

Javascript is an asynchronous programming language, so callbacks cannot be avoided. Front-end development often encounters scenarios like the following:

    $.ajax({
      url: '/ * * *'.success: function (res) {
        var xxId = res.id
        // The next interface parameter needs this ID
        $.ajax({
          url: '/ * * *'.data: {
            xxId: xxId, Call the next interface with the result of the previous request as an argument
          },
          success: function (res1) {}})Copy the code

When using Ajax to request interfaces, you write code like this to ensure that multiple interfaces execute in the specified order, and when you need to handle too many asynchronous callbacks, you need more layers of nesting, creating callback hell. This is when the code becomes difficult to maintain.

In ES6, the Promise object was introduced to solve the problem of callback hell. Promise is also the most widely used method in JS for handling asynchronous operations.

About callback functions

A function that is passed as an argument to another function and called inside that function to accomplish something is called a callback function.

    function greeting(name) {
      alert('Hello ' + name);
    }

    function processUserInput(callback) {
      var name = prompt('Please enter your name. ');
      callback(name);
    }

    processUserInput(greeting);
Copy the code

Synchronous callback, executed directly.

    var a = 0
    setTimeout(function () {
      a = 1
    }, 1000)
    // asynchronous callback
    setTimeout(function () {
      console.log(a)
    }, 2000)
Copy the code

The above code is an asynchronous callback.

The callback function structure in JS is synchronous by default. Due to the rules of the JavaScript single-threaded asynchronous model, if you want to write asynchronous code, you must use the form of callback nesting to achieve, so the callback function structure is not necessarily asynchronous code, but asynchronous code must be callback function structure.

Promise

A Promise is an object that represents the final completion or failure of an asynchronous operation.

instantiation

    // Instantiate a Promise object
    let p = new Promise(function (resolve, reject) {
        resolve("Success!!");
        // reject(" reject ");
    })
Copy the code

When a new Promise is passed in, the callback function is synchronized. Resolve () is called in the callback to indicate that the operation completed successfully and will be executed on the chain call. If you call reject(), the operation has failed, and.catch is executed on a chain call.

Chain call:

    p.then(function () {
      console.log('then,', res)
    }).catch(function () {
      console.log('catch')
    }).finally(function () {
      console.log('finally perform')})Copy the code

The.then.catch-finally callback functions are asynchronous. .finally is called whether the operation failed or succeeded.

Three states of Promise

  • Pending: Initial state. This is the state at the beginning of the Promise object definition, when a Promise simply initializes and registers all tasks on his object.

  • This is a big pity: This is fulfilled, which usually represents the successful execution of a task. When the resolve in the initialization function is fulfilled, the state of the Promise will change to fulfilled, and the callback registered by the then function will start executing, and the parameters passed in resolve will enter the callback function as parameters.

  • Rejected: Indicates that a task has been rejected or the process has been interrupted. When reject is called, the callback registered by the catch is fired and the parameters passed in reject become parameters of the callback function.

The relationship between the three states:

It is agreed in Promise that after the object is created, the same Promise object can only change from the pending state to one of depressing or Rejected. Once the state is changed, it will not change again. At this time, the process of the Promise object is completed and finally function is executed.

    let p = new Promise(function (resolve, reject) {
      resolve()
      reject()
    })
    p.then(function (res) {
      console.log('then execute')
    }).catch(function () {
      console.log('catch')
    }).finally(function () {
      console.log('finally perform')})Copy the code

The above code is executed in order, then -> finally. Because once the state is changed, it will not change again. After the implementation of resolve(), the state will become a pity and will not change again.

Promise object

    let p = new Promise(function (resolve, reject) {
      resolve("Promise object...")})console.log(p)
Copy the code

Executing this code will print:

  • [[Prototype]]The prototype object that represents the Promise
  • [[PromiseState]]Represents the current state of the Promise object, corresponding to the three states mentioned above
  • [[PromiseResult]]A value representing the Promise object, corresponding to the result passed in to resolve or Reject, respectively

Chain calls

Run the following code:

      let p = new Promise(function (resolve, reject) {
        resolve("I am resolve");
      });
      console.log(p);
      p.then(function (res) {
        console.log("1 -", res);
      })
        .then(function (res) {
          console.log("2 -", res);
          return "Step 2 Return value";
        })
        .then(function (res) {
          console.log("3 -", res);
          return new Promise(function (resolve) {
            resolve("Return new Promise return value");
          });
        })
        .then(function (res) {
          console.log("4 -", res);
          return "Step 4 Return value";
        })
        .then()
        .then("No return value")
        .then(function (res) {
          console.log("5 -", res);
        });
Copy the code

Watch the above code output on the console:

HTML :55 2-- undefined 2020. HTML :59 3-- Step 2 return return value 2020. HTML :65 4-- return new HTML :71 5-- Step 4 Return the value returnedCopy the code

Conclusion:

  • As long as there is then() and resolve is triggered, the chain is executed to the end, and the first callback in the process takes the value passed to resolve
  • Subsequent functions return a normal variable that is used as an argument to the next THEN callback function
  • Subsequent functions return a Promise object, and the result of that Promise’s resolve is taken as an argument to the next THEN callback
  • If no return is returned, the argument to the next THEN callback isundefined
  • If no function or value is passed in the THEN, the Promise chain does not interrupt the chained call to the THEN, and the last result returned before that goes directly to the nearest correct then callback as an argument

Interrupt the chain call

There are two ways to break the then chain:

      p.then(function (res) {
        console.log(res);
      })
        .then(function (res) {
          // There are two ways to interrupt promises
          // throw('throw throws exception interrupt ')
          return Promise.reject("Reject interrupt");
        })
        .then(function (res) {
          console.log(res);
        })
        .then(function (res) {
          console.log(res);
        })
        .catch(function (err) {
          console.log(err);
        });
Copy the code

Either throw or return promise.reject () can be used to interrupt then. No THEN is executed from the beginning of the interrupt to the middle of the catch, and the catch function is fired and the process ends.

Promise the related Api

Before we get to the Promise Api, let’s address the ajax callback hell problem mentioned in the introduction. How do you use Promise to solve the callback hell problem? Look at the following code:

      let p = new Promise(function (resolve, reject) {
          $.ajax({
            url: "/user".success: function (res1) { resolve(res1.id); }}); });// Call the next interface after getting the id
      p.then(function(xxId){
           $.ajax({
            url: "/userinfo".data: {
              xxId: xxId, Call the next interface with the result of the previous request as an argument
            },
            success: function (res2) {}}); })Copy the code

This solves the problem of callback hell mentioned in the introduction to this article. Here’s a look at the common Promise apis:

Promise.all()

As you can see from the above description, we can control asynchronous processes to execute in a specified order through promise.then. Consider a scenario where a page needs to be rendered after both interfaces have been successfully called to return data. Interface 1 takes 1s, interface 2 takes 0.6s, and it takes 1.6s to render the page using the above chain call. Use promise.all ().

      let p1 = new Promise((resolve, reject) = > {
        setTimeout(() = > {
          resolve("First promise fulfilled.");
        }, 1000);
      });
      let p2 = new Promise((resolve, reject) = > {
        setTimeout(() = > {
          resolve("The second promise is fulfilled.");
        }, 600);
      });

      let p = Promise.all([p1, p2])
        .then((res) = > {
          console.log(res);
        })
        .catch(function (err) {
          console.log(err);
        });
Copy the code

The code above outputs the result after 1s, meaning promise.all () will wait for the slowest interface to return data. Only when the states of P1 and P2 become progressively, the state of P will become progressively. At this time, the return values of P1 and P2 will form an array and be passed to the callback function of P.

Promise.race()

The race() method, like promise.all (), is used to wrap multiple Promise instances into a new Promise instance.

let p = Promise.race([p1, p2])
Copy the code

The difference is that the state of P changes if either instance of P1 or P2 changes state first. The return value of the first changed Promise instance is passed to the return value of P.

The Generator function

Promise is powerful, but if you use the then() function directly to make chain calls, the code is still very bloated, and you still need a lot of chain calls to support a very complex asynchronous process. It would be nice if there were a way to make asynchrony look like synchronization more explicitly.

ES6 has introduced Generator functions that suspend the execution flow of functions using the yield keyword, making it possible to change the execution flow, thus providing a solution for asynchronous programming. Its existence provides the ability for functions to be executed step by step.

Generator function composition

Generators differ from normal functions in that:

  • Function is preceded by a *
  • There’s a yield expression inside the function
      function* test(){
        yield 11;
        yield 12;
        yield "string";
      }
Copy the code

Enforcement mechanism

You can call a Generator just as you would a normal function. However, Generator functions do not execute immediately as normal functions do. Instead, they return a pointer to an internal state object, so you call next on the Iterator object. The pointer will be executed from the head of the function or from where it was last stopped.

      let gen = test();
      console.log(gen.next());
      console.log(gen.next());
      console.log(gen.next());
      console.log(gen.next());
Copy the code

Executing the code will print:

 {value: 11, done: false}
 {value: 12, done: false}
 {value: 'string', done: false}
 {value: undefined, done: true}
Copy the code

You can see that the next method returns an object, which can be retrieved using next().value. After the command is executed, done changes to true.

Asynchronous process synchronization

      function* test() {
        yield new Promise(function (resolve, reject) {
          setTimeout(function () {
            console.log("Delay of 3 seconds");
            resolve();
          }, 3000);
        });
        yield new Promise(function (resolve, reject) {
          setTimeout(function () {
            console.log("Delay of 2 seconds");
            resolve();
          }, 2000);
        });
      }
Copy the code

The above code, according to the normal logic, should be delayed 2 seconds part of the code output first, three seconds later output. We will convert the code of the asynchronous process to synchronous execution, from top to bottom, as follows:

      gen.next().value.then(function () {
        gen.next();
      });
Copy the code

Output:

HTML :55 delay 3 seconds. HTML :61 delay 2 secondsCopy the code

This changes the order of asynchronous execution, allowing a 3-second delay in output first.

It can also be encapsulated as a utility function to handle more complex asynchronous processes. Here is a simple function that does not take into account some boundary cases such as exceptions.

      // a simple utility function
      function toSync(gen) {
        const item = gen.next();
        if (item.done) {
          return item.value;
        }

        const { value, done } = item;
        if (value instanceof Promise) {
          value.then((e) = > toSync(gen));
        } else{ toSync(gen); }}Copy the code

Using functions:

    toSync(test());
Copy the code

The output is the same as above.

The above is a transition to asynchronous JavaScript programming, and async and await are the most popular ways to handle asynchronous programming.

Aync & Await

JS asynchronous programming is a relatively troublesome thing, people have been looking for solutions. From the earliest callback functions, to the Promise object, to the Generator, each time improved, but felt incomplete. The AYSNC function, released in ES8, is considered by most to be the ultimate solution for asynchronous programming, and is the one most used today.

You can think of async functions as syntactic sugar for Generator functions.

Learn about async functions

Create a function

      async function test() {
        return 1;
      }
      let res = test();
      console.log(res);
Copy the code

View console output:

Promise {<fulfilled>: 1}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 1
Copy the code

If YOU look at the console it turns out that the async modified function is itself a Promise object.

await

  • Async might have oneawaitExpressions async functions are executed when encounteredawaitAfter the async operation is complete, the async function is resumed and the parsed value is returned.
  • awaitThe keyword is valid only in async function.
  • Under normal circumstances,awaitThe command is followed by a Promise object, which can also be followed by other values such as strings, booleans, numeric values, and ordinary functions. If it is not a Promise object, the relevant value is returned directly.

Take a look at the following example:

      async function test() {
        await new Promise((resolve) = >{
          setTimeout(() = >{
            console.log(4);
            resolve();
          },2000)})let a = await 2;
        console.log(a);
      }
      console.log(1);
      test();
      console.log(3);
Copy the code

Output sequence 1,3,4,2. “Test ()”, “test”, “await”, “test”, “await”, “test”, “await”, “test”, “await”, “test”, “await” Then execute two await in ascending order, output 4 two seconds later, followed by output 2.

Here’s another example:

      async function test() {
        var res1 = await new Promise(function (resolve) {
          setTimeout(function () {
            resolve("First second run");
          }, 1000);
        });
        console.log(res1);
        var res2 = await new Promise(function (resolve) {
          setTimeout(function () {
            resolve("Second run");
          }, 1000);
        });
        console.log(res2);
        var res3 = await new Promise(function (resolve) {
          setTimeout(function () {
            resolve("Third second run");
          }, 1000);
        });
        console.log(res3);
      }
      test();
Copy the code

Output:

HTML :52 Run the 2022. HTML :58 run the 2022. HTML :64 run the thirdCopy the code

The above code is printed every second.

As you can see from the example above, code can be synchronized automatically with async and await. Async is also the most frequently used syntax because asynchronous processes can be controlled with much cleaner code.

In the previous

JS asynchronous programming (I) event cycle model