preface

In the early Web applications, when interacting with the background, you need to submit a form and then give feedback to the user after the page is refreshed. In the page refresh process, the background will return a section of HTML code, most of the content of this section of HTML is basically the same as the previous page, which is bound to cause traffic waste, and also prolong the response time of the page, always make people feel that the experience of the Web application is not as good as the client application.

In 2004, AJAX, the “Asynchronous JavaScript and XML” technology, came along and dramatically improved the Web application experience. Then in 2006, jQuery came along and took the Web application development experience to a new level.

Due to the single-threaded nature of JavaScript, both events and AJAX are triggered by callback for asynchronous tasks. If we wanted to handle multiple asynchronous tasks linearly, we would have something like this in our code:

getUser(token, function (user) {
  getClassID(user, function (id) {
    getClassName(id, function (name) {
      console.log(name)
    })
  })
})
Copy the code

We often refer to this code as “callback hell.”

Events and callbacks

As we all know, the runtime of JavaScript runs on a single thread, which is based on the event model for asynchronous task triggering. There is no need to consider the issue of shared memory locking, and the bound events will be triggered orderly. To understand asynchronous tasks in JavaScript, you first need to understand JavaScript’s event model.

Since it is an asynchronous task, we need to organize a piece of code to run in the future (at the end of a specified time or when an event is triggered), and this piece of code is usually put into an anonymous function, usually called a callback function.

setTimeout(function () {
  // The callback that is triggered when the specified time ends},800)
window.addEventListener("resize".function() {
  // The callback that is triggered when the browser window changes
})
Copy the code

The future operation

The fact that the callback function runs in the future indicates that the variables used in the callback are not fixed at the callback declaration stage.

for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log("i =", i)
  }, 100)}Copy the code

Here we declare three asynchronous tasks in a row. After 100 milliseconds, the output of variable I should be 0, 1, and 2.

However, this is not the case, and this is the problem we encountered when we first started JavaScript, because the actual time of the callback is in the future, so the output value of I is the value at the end of the loop, and the result of all three asynchronous tasks is the same, which is the output of three I = 3.

Those of you who have been through this, you know that you can solve this problem by closing it, or by redeclaring local variables.

The event queue

Event, after all of the callback function will be stored, and then in the process of running, there will be another thread scheduling of the asynchronous calls the callback processing, once the “trigger” conditions will be the callback function into the corresponding event queue (here is simply to understand into a queue, exist two event queue: micro macro task, task).

The triggering conditions are generally met in the following situations:

  1. Events that trigger DOM related operations, such as clicks, moves, out-of-focus, and so on;
  2. IO related operations, such as file reading completion, network request end, etc.
  3. Time dependent operations that arrive at the agreed time of a scheduled task;

When this happens, the previously specified callback function in the code is put into a task queue, and once the main thread is idle, the tasks are executed on a first-in, first-out basis. When a new event is triggered, it is put back into the callback and so on 🔄, so this mechanism of JavaScript is often referred to as the “event loop mechanism”.

for (var i = 1; i <= 3; i++) {
  const x = i
  setTimeout(function () {
    console.log(The first `${x}A setTimout is executed)},100)}Copy the code

As can be seen, the running order meets the characteristics of queue first-in, first-out, and the first declared is executed first.

Thread blocking

Due to the single-threaded nature of JavaScript, timers are not reliable. When the code is blocked, even if the event reaches the trigger time, it will wait until the main thread is idle before running.

const start = Date.now()
setTimeout(function () {
  console.log('Actual waiting time:The ${Date.now() - start}ms`)},300)

// The while loop blocks the thread for 800ms
while(Date.now() - start < 800) {}
Copy the code

In the above code, the timer is set to 300ms before triggering the callback function. If the code is not blocked, it normally outputs the waiting time after 300ms.

However, we did not add a while loop, which will end after 800ms. The main thread is blocked by this loop, causing the callback function to not run properly when the time is up.

Promise

The way events are called back, during coding, is especially easy to create callback hell. Promise offers a more linear way to write asynchronous code, somewhat like a pipeline mechanism.

// Call back to hell
getUser(token, function (user) {
  getClassID(user, function (id) {
    getClassName(id, function (name) {
      console.log(name)
    })
  })
})

// Promise
getUser(token).then(function (user) {
  return getClassID(user)
}).then(function (id) {
  return getClassName(id)
}).then(function (name) {
  console.log(name)
}).catch(function (err) {
  console.error('Request exception', err)
})
Copy the code

Promise has similar implementations in many languages, and in the development of JavaScript, jQuery and Dojo, some of the more famous frameworks, also have similar implementations. In 2009, CommonJS introduced the Promise/A specification based on dojo. Deffered implementation. It was also the year that Node.js was born. Many implementations of Node.js are based on the CommonJS specification, and the modular solution is more familiar.

Promise objects were implemented in earlier Versions of Node.js, but in 2010, Ry (author of Node.js) decided that Promise was an upper-level implementation and that Node.js was built on V8 engines. The V8 engine also didn’t provide Promise support natively, so later Node.js modules used the error-first callback style (CB (error, result)).

const fs = require('fs')
// The first argument is an Error object. If it is not empty, an exception occurs
fs.readFile('./README.txt'.function (err, buffer) {
  if(err ! = =null) {
    return
  }
  console.log(buffer.toString())
})
Copy the code

This decision also led to the emergence of various Promise libraries in Node.js, notably Q.js and Bluebird. On the Promise implementation, there was an article written before, interested in: “Hand to teach you to make Promises”.

Prior to Node.js@8, the V8 native Promise implementation had some performance issues, resulting in native promises performing worse than even some third-party Promise libraries.

As a result, promises are often replaced globally in earlier Node.js projects:

const Bulebird = require('bluebird')
global.Promise = Bulebird
Copy the code

Generator & co

Generator is a new function type provided by ES6 that is used to define a function that can iterate on itself. The syntax of function * allows you to construct a Generator function that returns an Iteration (iterator) object with a next() method that is paused at yield each time it is called. Until the next() method is called again.

function * forEach(array) {
  const len = array.length
  for (let i = 0; i < len; i ++) {
    yieldi; }}const it = forEach([2.4.6])
it.next() // { value: 2, done: false }
it.next() // { value: 4, done: false }
it.next() // { value: 6, done: false }
it.next() // { value: undefined, done: true }
Copy the code

The next() method returns an object with two properties value, done:

  • valueSaid:yieldThe following value;
  • done: indicates whether the function is completed.

Since generator functions have the characteristics of interrupted execution, generator functions can be treated as a container for asynchronous operations, and then methods of Promise objects can be returned to the execution of asynchronous logic. After each yeild, a Promise object can be added to make iterators continue to execute.

function * gen(token) {
  const user = yield getUser(token)
  const cId = yield getClassID(user)
  const name = yield getClassName(cId)
  console.log(name)
}

const g = gen('xxxx-token')

// The value returned by executing the next method is a Promise object
const { value: promise1 } = g.next()
promise1.then(user= > {
  // The value passed to the second next method will be accepted by the variable before the first yield keyword in the generator
  // The same goes for pushing back. The value of the third next method is accepted by the variable before the second yield
  // Only the value of the first next method is discarded
  const { value: promise2 } = gen.next(user).value
  promise2.then(cId= > {
    const { value: promise3, done } = gen.next(cId).value
    // Pass in sequence until the next method returns done true})})Copy the code

We abstracted the above logic so that after each Promise object returns normally, next is called automatically, and the iterator executes itself until it completes (that is, done is true).

function co(gen, ... args) {
  constg = gen(... args)function next(data) {
    const { value: promise, done } = g.next(data)
    if (done) return promise
    promise.then(res= > {
      next(res) // Pass the result of a promise to the next yield
    })
  }
  
  next() // Start automatic execution
}

co(gen, 'xxxx-token')
Copy the code

This is the implementation logic of CO, koA’s early core library, except that CO does some parameter verification and error handling. Adding CO to generator to make asynchronous processes easier to read is definitely a good thing for developers.

async/await

Async /await is the ultimate solution to asynchronous JavaScript programming. It is essentially a syntactic sugar for Generator & co. Simply add async before asynchronous Generator functions and replace yield with await in Generator functions.

async function fun(token) {
  const user = await getUser(token)
  const cId = await getClassID(user)
  const name = await getClassName(cId)
  console.log(name)
}

fun()
Copy the code

Async functions have self-executors built in, and await is not restricted to Promise objects, which can be any value, and async/await is semantically clearer than yield on generators, so it is immediately obvious that this is an asynchronous operation.