Six Schemes of JS Asynchronous Programming

  es6, Front end, javascript, node.js, Programmer

Preface

We know that the execution environment of Javascript language is “single thread”. This means that only one task can be completed at a time. If there are multiple tasks, you must queue up, the previous task is completed, and the next task is executed.

Although this mode is relatively simple to implement and the execution environment is relatively simple, as long as there is one task that takes a long time, the following tasks must wait in line, which will delay the execution of the entire program. The common browser does not respond (fake death), which is often because a piece of Javascript code runs for a long time (such as a dead loop), causing the entire page to get stuck in this place and other tasks cannot be performed.

In order to solve this problem, Javascript language divides the execution modes of tasks into two types: synchronous and asynchronous. This article mainly introduces several methods of asynchronous programming, and through comparison, obtains the best asynchronous programming solution!

If you want to read more excellent articles, please stamp them fiercely.GitHub blog

First, synchronous and asynchronous

We can generally understand asynchrony as a task that is divided into two sections, first executing the first section, then executing other tasks, and then returning to the second section when we are ready. The code that follows the asynchronous task will run immediately without waiting for the asynchronous task to end, that is to say,Asynchronous tasks do not have “blocking” effect. For example, there is a task to read files for processing. The asynchronous execution process is as follows

This discontinuous execution is called asynchronous. Accordingly, continuous execution is called synchronization.

Asynchronous mode is very important. On the browser side, time-consuming operations should be executed asynchronously to prevent the browser from losing its response. The best example is Ajax operations. On the server side, “asynchronous mode” is even the only mode, because the execution environment is single-threaded. If all http requests are allowed to be executed synchronously, the server performance will drop sharply and the response will soon be lost. Next, six methods of asynchronous programming are introduced.

Second, the Callback function (callback)

Callback function is the most basic method for asynchronous operation. The following code is an example of a callback function:

ajax(url, () => {
 //processing logic
 })

But callback function has a fatal weakness, is easy to writeCallback hell. Assuming multiple requests are dependent, you might write the following code:

ajax(url, () => {
 //processing logic
 ajax(url1, () => {
 //processing logic
 ajax(url2, () => {
 //processing logic
 })
 })
 })

The advantages of callback functions are simple, easy to understand and implement, while the disadvantages are not conducive to code reading and maintenance. The high coupling between various parts makes the program structure chaotic and the process difficult to track (especially when multiple callback functions are nested), and each task can only specify one callback function. In addition, it cannot use try catch to catch errors and cannot return directly.

Iii. incident monitoring

In this way,The execution of asynchronous tasks does not depend on the sequence of codes, but on whether an event occurs or not..

The following are two functions f1 and f2. The intention of programming is that f2 cannot be executed until f1 is executed. First, bind an event for f1 (jQuery is used here)

f1.on('done', f2);

The above line of code means that when f1 has done, f2 is executed. Then, rewrite f1:

function f1() {
 setTimeout(function () {
 //   ...
 f1.trigger('done');
 }, 1000);
 }

In the above code, f1.trigger(‘done’) indicates that the done event will be triggered immediately after the execution is completed, thus starting to execute f2.

The advantage of this method is that it is relatively easy to understand, can bind multiple events, each event can specify multiple callback functions, and can be “decoupled”, which is conducive to the realization of modularity. The disadvantage is that the whole program will become event-driven and the running process will become very unclear. When reading the code, it is difficult to see the main process.

IV. Publish and Subscribe

We assume that there is a “signal center”. When a task is completed, it will “publish” a signal to the signal center. Other tasks can “subscribe” to the signal center to know when they can start executing. This is called “publish-subscribe pattern”, also known as “observer pattern”.

First, f2 subscribes to the done signal from the signal center jQuery.

jQuery.subscribe('done', f2);

Then f1 is rewritten as follows:

function f1() {
 setTimeout(function () {
 //   ...
 jQuery.publish('done');
 }, 1000);
 }

In the above code, jQuery.publish(‘done’) means that after f1 execution is completed, the done signal is issued to jQuery, the signal center, thus triggering f2 execution.
F2 can unsubscribe after execution

jQuery.unsubscribe('done', f2);

The nature of this method is similar to “event monitoring”, but obviously superior to the latter. Because you can monitor the operation of the program by looking at the “message center” to know how many signals exist and how many subscribers there are for each signal.

V. Promise/A+

Promise means to promise that I will give you a result after a period of time. When will it take some time? The answer is asynchronous operation. Asynchronous refers to operations that may take a long time to produce results, such as network requests, reading local files, etc.

1. Three states of 1.Promise

  • Pending-Initial state of Promise object instance at creation time
  • Fulfilled—- can be understood as a state of success.
  • Rejected—- can be understood as a state of failure

Once this promise changes from waiting state to other state, it can never change the state.For example, once the state is resolved, it cannot be changed to full again.

let p = new Promise((resolve, reject) => {
 reject('reject')
 Resolve('success')// Invalid code will not be executed
 })
 p.then(
 value => {
 console.log(value)
 },
 reason => {
 console.log(reason)//reject
 }
 )

When we construct Promise, the code inside the constructor is executed immediately

new Promise((resolve, reject) => {
 console.log('new Promise')
 resolve('success')
 })
 console.log('end')
 // new Promise => end

2. Chain call of 2.promise

  • Each call returns a new Promise instance (which is why then can use chain calls)
  • If a result is returned in then, the result will be passed to the next successful callback in then.
  • If there is an exception in then, the next then failure callback will be taken.
  • If return is used in then, the value of return will be wrapped by Promise.resolve () (see examples 1 and 2)
  • Parameters may not be passed in then, and if they are not passed, they will pass through to the next then (see Example 3)
  • Catch will catch the exception that was not caught.

Let’s look at a few examples:

//Example 1
 Promise.resolve(1)
 .then(res => {
 console.log(res)
 Return 2 // packaged as Promise.resolve(2)
 })
 .catch(err => 3)
 .then(res => console.log(res))
//Example 2
 Promise.resolve(1)
 .then(x => x + 1)
 .then(x => {
 throw new Error('My Error')
 })
 .catch(() => 1)
 .then(x => x + 1)
 .then(x => console.log(x)) //2
 .catch(console.error)
//Example 3
 let fs = require('fs')
 function read(url) {
 return new Promise((resolve, reject) => {
 fs.readFile(url, 'utf8', (err, data) => {
 if (err) reject(err)
 resolve(data)
 })
 })
 }
 read('./name.txt')
 .then(function(data) {
 An exception occurred in throw new Error() //then, which will lead to the next then failure callback.
 })//Since the next then has no failed callback, it will continue to look down. If none exists, it will be caught by catch.
 .then(function(data) {
 console.log('data')
 })
 .then()
 .then(null, function(err) {
 console.log('then', err)// then error
 })
 .catch(function(err) {
 console.log('error')
 })

Promise can not only capture errors, but also solve the problem of callback hell. The previous callback hell example can be rewritten into the following code:

ajax(url)
 .then(res => {
 console.log(res)
 return ajax(url1)
 }).then(res => {
 console.log(res)
 return ajax(url2)
 }).then(res => console.log(res))

It also has some shortcomings, such as unable to cancel Promise, and errors need to be captured through callback functions.

VI. Generators/ yield

Generator function is an asynchronous programming solution provided by ES6. Its syntax behavior is completely different from that of traditional functions. The greatest feature of Generator is that it can control the execution of functions.

  • Syntactically, it can be first understood as a Generator function is a state machine that encapsulates multiple internal states.
  • The Generator function is not only a state machine, but also a traverser object generation function..
  • The function can be paused, yield can be paused, the next method can be started, and the expression result after yield is returned each time.
  • The yield expression itself does not return a value, or always returns undefined.The next method can take a parameter, which is treated as the return value of the previous yield expression.

Let’s look at an example first:

function *foo(x) {
 let y = 2 * (yield (x + 1))
 let z = yield (y / 3)
 return (x + y + z)
 }
 let it = foo(5)
 console.log(it.next())   // => {value: 6, done: false}
 console.log(it.next(12)) // => {value: 8, done: false}
 console.log(it.next(13)) // => {value: 42, done: true}

Perhaps the result is not consistent with your imagination. Next we will analyze the code line by line:

  • First, unlike ordinary functions, the Generator function call returns an iterator
  • When the first next is executed, the transfer session is ignored, and the function pauses at yield (x+1), so return 5+1 = 6
  • When performing the second next, the passed-in parameter 12 will be taken as the return value of the previous yield expression. If you do not pass in the parameter, yield will always return undefined. Let y = 2 at this time12, so the second yield is equal to 212 / 3 = 8
  • When performing the third next, the passed-in parameter 13 will be taken as the return value of the previous yield expression, so z = 13, x = 5, y = 24, and the sum equals 42

Let’s look at another example: there are three local files, 1.txt,2.txt and 3.txt, which have only one sentence. The next request depends on the result of the previous request. We want to call the three files in turn through the Generator function.

//1.txt file
 2.txt
//2.txt file
 3.txt
//3.txt file
 End
let fs = require('fs')
 function read(file) {
 return new Promise(function(resolve, reject) {
 fs.readFile(file, 'utf8', function(err, data) {
 if (err) reject(err)
 resolve(data)
 })
 })
 }
 function* r() {
 let r1 = yield read('./1.txt')
 let r2 = yield read(r1)
 let r3 = yield read(r2)
 console.log(r1)
 console.log(r2)
 console.log(r3)
 }
 let it = r()
 let { value, done } = it.next()
 Value.then (function) (data) {//value is a promise
 console.log(data) //data=>2.txt
 let { value, done } = it.next(data)
 value.then(function(data) {
 console.log(data) //data=>3.txt
 let { value, done } = it.next(data)
 value.then(function(data) {
 Log (data)//data = > end
 })
 })
 })
 // 2.txt=>3.txt= > end

From the above example, we can see manual iterationGeneratorFunctions are very troublesome, and the implementation logic is somewhat convoluted, while actual development will generally be coordinated.coLibrary to use.coIs a generator-based process control tool for Node.js and browsers. with Promise, you can write non-blocking code in a more elegant way.

InstallationcoThe library only needs to:npm install co

The above example can be easily realized in two sentences.

function* r() {
 let r1 = yield read('./1.txt')
 let r2 = yield read(r1)
 let r3 = yield read(r2)
 console.log(r1)
 console.log(r2)
 console.log(r3)
 }
 let co = require('co')
 co(r()).then(function(data) {
 console.log(data)
 })
 // 2.txt=>3.txt= > end =>undefined

We can solve the problem of callback hell through the Generator function, and can rewrite the previous callback hell example into the following code:

function *fetch() {
 yield ajax(url, () => {})
 yield ajax(url1, () => {})
 yield ajax(url2, () => {})
 }
 let it = fetch()
 let result1 = it.next()
 let result2 = it.next()
 let result3 = it.next()

Async/await

Introduction to 1.Async/Await

With async/await, you can easily accomplish what you did with the generator and the co function. It has the following characteristics:

  • Async/await is implemented based on Promise and cannot be used for ordinary callback functions.
  • Async/await, like Promise, is non-blocking.
  • Async/await makes asynchronous code look like synchronous code, which is its magic.

If async is added to a function, the function will return a Promise.

async function async1() {
 return "1"
 }
 console.log(async1()) // -> Promise {<resolved>: "1"}

The Generator function calls three files in turn. The example is written in async/await and can be realized in a few words.

let fs = require('fs')
 function read(file) {
 return new Promise(function(resolve, reject) {
 fs.readFile(file, 'utf8', function(err, data) {
 if (err) reject(err)
 resolve(data)
 })
 })
 }
 async function readResult(params) {
 try {
 Letp1 = Await Read (params,' utf8')//Await is followed by a Promise instance
 let p2 = await read(p1, 'utf8')
 let p3 = await read(p2, 'utf8')
 console.log('p1', p1)
 console.log('p2', p2)
 console.log('p3', p3)
 return p3
 } catch (error) {
 console.log(error)
 }
 }
 Readresult ('1.txt'). then (/async function returns a promise
 data => {
 console.log(data)
 },
 err => console.log(err)
 )
 // p1 2.txt
 // p2 3.txt
 // p3 End
 //End

2.Async/Await concurrent request

If you request two files, there is no relationship, you can request them concurrently.

let fs = require('fs')
 function read(file) {
 return new Promise(function(resolve, reject) {
 fs.readFile(file, 'utf8', function(err, data) {
 if (err) reject(err)
 resolve(data)
 })
 })
 }
 function readAll() {
 read1()
 Read2()// This function executes synchronously
 }
 async function read1() {
 let r = await read('1.txt','utf8')
 console.log(r)
 }
 async function read2() {
 let r = await read('2.txt','utf8')
 console.log(r)
 }
 readAll() // 2.txt 3.txt

Viii. summary

1.JS asynchronous programming evolution history: callback-> promise-> generator-> async+away

The realization of the 2.async/await function is to wrap the Generator function and the automatic actuator in a function.

3.async/await can be said to be the ultimate asynchronous solution.

(1) async/await function has the following advantages over Promise

  • Handling then’s call chain can write code more clearly and accurately
  • And it can also gracefully solve the problem of callback to hell.

Of course, async/await function also has some shortcomings, because await has transformed asynchronous code into synchronous code. If multiple asynchronous codes have no dependency but use await will lead to performance degradation, if the code does not have dependency, it is completely possible to use Promise.all.

(2) async/await function improves the Generator function in the following three points

  • Built-in actuator.

The execution of the Generator function must depend on the executor, so there is a co function library, while the async function has its own executor. That is to say,Async functions are executed exactly like normal functions, with only one line required..

  • Wider applicability. Co function library convention, yield command can only be followed by Thunk function or Promise object, andThe await command of the async function can be followed by a Promise object and values of the original type (numeric, string, and Boolean values, but this is equivalent to a synchronization operation).
  • Better semantics. Async and await have clearer semantics than asterisks and yield. Async indicates that there are asynchronous operations in the func tion, await indicates that the expression immediately following needs to wait for the result.

Reference article