The syntactic goal of asynchronous programming is, how do you make it more like synchronous programming

What is asynchrony?

Asynchronous tasks are tasks that do not enter the main thread but enter the task queue. The task queue notifies the main thread that an asynchronous task is ready to execute. Asynchronous execution works as follows:

  1. All synchronization tasks are executed on the main thread, forming an execution Context stack.
  2. In addition to the main thread, there is a “task queue”. Whenever an asynchronous task has a result, an event is placed in the “task queue”.
  3. Once all synchronization tasks in the execution stack are completed, the system reads the task queue to see what events are in it. Those corresponding asynchronous tasks then end the wait state, enter the execution stack, and start executing.
  4. The main thread repeats step 3 above.


1. Callback functions

Scenario: Reading a file

let fs = require('fs')
fs.readFile('./1.txt'.'utf8'.function(err, data){
    // The characteristic of a callback is that the first argument is usually an error object
    if (err) { 
        // If err has a value, the program is in error
        console.log(err)
    } else { 
        // Otherwise, data is successfully obtained
        console.log(data)
    }
})
Copy the code

Callbacks also have their drawbacks:

  • Unable to catch errors (using try catch)
funnction readFile (fileName) {
    fs.readFile(fileName, 'utf8'.function (data) {
        if (err) {
            console.log(err)
        } else {
            console.log(data)
        }
    })
}
try {
    readFile('./1.txt')}catch (e) { 
    // If there is an error in reading the file, no error message can be obtained
    console.log('err', e)
}
Copy the code
  • Can’t returnThe readFile method cannot return the contents of the readFile (data)
  • The callback hell
fs.readFile('./data1.txt'.'utf8'.function (err, data1) {
  fs.readFile('./data2.txt'.'utf8'.function (err, data2) {
    fs.readFile('./data3.txt'.'utf8'.function (err, data3) {
      fs.readFile('./data4.txt'.'utf8'.function (err, data4) {
        fs.readFile('./data5.txt'.'utf8'.function (err, data5) {
            console.log(data1, data2, data3, data4, data5)
        })
      })
    })
  })
})
// This code is called the Demon pyramid; And have the following problems
// The code is ugly
// 2, difficult to maintain
// 3, the efficiency is low because they are serial; Only one file can be requested at a time
Copy the code


2. Event publishing and subscription

To solve the problem of nested callbacks

let EventEmitter = require('events')
// One of the core nodejs modules, which contains two core methods:
// on >> registers the listener, and emit >> emits an event

let eve = new EventEmitter()
let html = {} // Store page templates and data
eve.on('reading'.function (key, value) {
    html[key] = value
    if (Object.keys(html).length == 2) {
        console.log(html)
    }
})
fs.readFile('./template.txt'.'utf8'.function (err, template) {
    eve.emit('reading', template) 
    // Trigger the Reading event, execute the event's callback function to fill the HTML template
})
fs.readFile('./data.txt'.'utf8'.function (err, template) {
    eve.emit('reading', template) 
    // Triggers the Reading event, which executes the event's callback function to fill the HTML with data
})
Copy the code


3. Sentinel variables

Event publishing subscriptions already solve the problem of nested callbacks, but you need to introduce the Events module; Using sentry variables can also solve the problem of nested callbacks without introducing additional modules

// Define a sentry function to handle
function done (key, value) {
    html[key] = value
    if (Object.keys(html).length == 2) {
        console.log(html)
    }
}

fs.readFile('./template.txt'.'utf8'.function (err, template) {
    done('template', template)
})
fs.readFile('./data.txt'.'utf8'.function (err, template) {
    done('data', data)
})

// Can encapsulate a higher order function to generate the sentry function
function render (length, cb) {
    let htm = {}
    return function (key, value) {
        html[key] = value
        if (Object.keys(html).length == length) {
            cb(html)
        }
    }
}

let done = render(2.function (html) {
    console.log(html)
})
Copy the code


4, Promise

All of the above methods use callback functions to handle asynchrony; Our goal is to move asynchrony toward synchronization

// The use of promise is not stated
let promise1 = new Promise(function (resolve, reject) {
    fs.readFile('./1.txt'.'utf8'.function (err, data) {
        resolve(data)
    })
})

promise1.then(function (data) {
    console.log(data)
})
Copy the code


5. Generator

When we call a function, it does not execute immediately, but requires us to iterate manually (the next method); In simple terms, calling a generator function returns an iterator that can be used to iterate over each breakpoint (yield). Calling next returns value, which is the output of the generator function; The next method can also accept arguments, which are inputs into the generator function

  • Simple use of generators
// The method name preceded by * is a generator function
function *foo () {
  var index = 0;
  while (index < 2) {
    yield index++; // Suspend the function execution and perform the operation after yield}}var bar =  foo(); // Returns an iterator

console.log(bar.next());    // { value: 0, done: false }
console.log(bar.next());    // { value: 1, done: false }
console.log(bar.next());    // { value: undefined, done: true }
Copy the code
  • Generator + Promise addresses asynchronous implementations
function readFile (filaName) {
    return new Promise(function (resolve, reject) {
        fs.readFile(filename, function (err, data) {
          if (err) {
            reject(err)
          } else {
            resolve(data)
          }
    })
}
function *read() {
    let template = yield readFile('./template.txt')
    let data = yield readFile('./data.txt')
    return {
        template: template,
        data: data
    }
}
// Generate the iterator R1
let r1 = read()
let templatePromise = r1.next().value
templatePromise.then(function(template) {
    // Pass the contents of the obtained template to the generator function
    let dataPromsie = r1.next(template).value
    dataPromise.then(function(data) {
        // Next passes in the value of data for the last time; Finally return {template, data}
        let result = r1.next(data).value
        console.log(result)
    })
})
Copy the code
  • The generator + Promise implementation already has a synchronous look; The above code can be written elegantly with the help of some tools (CO)
// Implement the co method
// Argument is a generator function
function co (genFn) {
    let r1 = genFn()
    return new Promise(function(resolev, reject) {!function next(lastVal) {
            let p1 = r1.next(lastVal)
            if (p1.done) {
                resolve(p1.value)
            } else {
                p1.value.then(next, reject)
            }
        }()
    })
}

// Now get the above result
co(read).then(function(result) {
    console.log(result)
})
Copy the code


6, Async/await

Async is a syntactic sugar that is implemented by wrapping Generator functions and auto-actuators (CO) in a single function

// Implement the co method
// Argument is a generator function
async function read() {
  let template = await readFile('./template.txt');
  let data = await readFile('./data.txt');
  return template + '+' + data;
}

/ / is equivalent to
function read(){
  return co(function* () {
    let template = yield readFile('./template.txt');
    let data = yield readFile('./data.txt');
    return template + '+' + data;
  });
}
Copy the code


The goal of the development of asynchronous programming is to make asynchronous logic code look like synchronization. The development of Async/await is a milestone in dealing with asynchronous programming.


Reference article:

  • A brief history of asynchronous js development