Talk about several ways to deal with JavaScript asynchronous operations

introduction

The asynchronous operation of js is already a common topic, and you can see a lot of articles on this topic by google. So why am I even writing this? In recent work, in order to write a relatively complex set of plugins, various asynchronous operations need to be handled. However, for the sake of volume and compatibility, we do not plan to introduce any pollyfill, and even babel is not allowed to be used, which means that it can only be handled in the way of es5. Using callbacks is very bad for decoupling, so I found other ways to deal with this problem. The problem is that it has been dealt with, but it has also led to some thoughts: what are some ways to deal with the asynchronous operation of js?

1. Callback function

The legendary "callback hell" comes from the callback function. The callback function is also the most basic and most commonly used method to handle js asynchronous operations. Let's look at a simple example:

First define three functions:

function fn1 () {
  console.log('Function 1')
}

function fn2 () {
  setTimeout(() => {
    console.log('Function 2')
  }, 500)
}

function fn3 () {
  console.log('Function 3')
}

Which fn2can be thought of as an asynchronous function that executes with a delay of 500 milliseconds. fn1Now I want to be able to execute , fn2, , in sequence fn3. In order to ensure that fn3it is executed at the end, we can use it as fn2a callback function:

function fn2 (f) {
  setTimeout(() => {
    console.log('Function 2')
    f()
  }, 500)
}

fn2(fn3)

As you can see, fn2and fn3fully coupled together, if there are multiple similar functions, this is likely to happen fn1(fn2(fn3(fn4(...)))). I will not go into details about the disadvantages of callback hell. I believe that readers must have their own experience.

2. Event publish/subscribe

The publish/subscribe pattern is also one of many design patterns, and it happens that this method can handle asynchronous operations quite elegantly under es5. What is publish/subscribe? For the example in the above section, fn1, fn2, fn3can be regarded as an event publisher, as long as it is executed, an event will be published. At this time, we can subscribe to and process these events in batches through an event subscriber, including their order. Below we add a message subscriber method based on the example in the previous chapter (for simplicity, the code is written in es6):

class AsyncFunArr {
  constructor (...arr) {
    this.funcArr = [...arr]
  }

  next () {
    const fn = this.funcArr.shift()
    if (typeof fn === 'function') fn()
  }

  run () {
    this.next()
  }
}

const asyncFunArr = new AsyncFunArr(fn1, fn2, fn3)

Then call its method inside fn1, :fn2fn3next()

function fn1 () {
  console.log('Function 1')
  asyncFunArr.next()
}

function fn2 () {
  setTimeout(() => {
    console.log('Function 2')
    asyncFunArr.next()
  }, 500)
}

function fn3 () {
  console.log('Function 3')
  asyncFunArr.next()
}

// output =>
// Function 1
// Function 2
// Function 3

As you can see, the processing order of the function is equal to AsyncFunArrthe incoming parameter order. AsyncFunArrAn array is maintained internally, and each time next()the method is called, an object stored in the array will be pushed out in order and executed, which is also the method I use more often in my actual work.

3. Promises

Using the Promise method, there is no need to write an additional message subscriber function, only the asynchronous function returns a Promise. Let's look at the example:

function fn1 () {
  console.log('Function 1')
}

function fn2 () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Function 2')
      resolve()
    }, 500)
  })
}

function fn3 () {
  console.log('Function 3')
}

Similarly, we have defined three functions, one of which fn2is an asynchronous function that returns a Promise, and now we want to execute them in order, just do the following:

fn1()
fn2().then(() => { fn3() })

// output =>
// Function 1
// Function 2
// Function 3

There are two biggest differences between using Promises and callbacks. The first is fn2that fn3it can be decoupled from it; the second is that function nesting is changed to chained calls, which is more friendly to developers in terms of semantics and writing.

Four, generator

If the use of Promises can turn callbacks into chains, then the generator method can eliminate a lot of Promise feature methods, such as a lot of them then().

function fn1 () {
  console.log('Function 1')
}

function fn2 () {
  setTimeout(() => {
    console.log('Function 2')
    af.next()
  }, 500)
}

function fn3 () {
  console.log('Function 3')
}

function* asyncFunArr (...fn) {
  fn[0]()
  yield fn[1]()
  fn[2]()
}

const af = asyncFunArr(fn1, fn2, fn3)

af.next()

// output =>
// Function 1
// Function 2
// Function 3

In this example, the generator function asyncFunArr()accepts a list fnof functions to execute, through which the async function will yieldbe executed. Inside the async function, by af.next()activating the next action of the generator function.

At such a rough look, it is actually very similar to the publish/subscribe model, in which the subscriber is told to perform the next operation by actively calling the method inside the asynchronous function. But this method is still not elegant. For example, if there are multiple asynchronous functions, the generator function must be rewritten, and it is also a bit unintuitive in terms of semantics.

5. Elegant async/await

使用最新版本的Node已经可以原生支持async/await写法了,通过各种pollyfill也能在旧的浏览器使用。那么为什么说async/await方法是最优雅的呢?且看代码:

function fn1 () {
  console.log('Function 1')
}

function fn2 () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Function 2')
      resolve()
    }, 500)
  })
}

function fn3 () {
  console.log('Function 3')
}

async function asyncFunArr () {
  fn1()
  await fn2()
  fn3()
}

asyncFunArr()

// output =>
// Function 1
// Function 2
// Function 3

有没有发现,在定义异步函数fn2的时候,其内容和前文使用Promise的时候一模一样?再看执行函数asyncFunArr(),其执行的方式和使用generator的时候也非常类似。

异步的操作都返回Promise,需要顺序执行时只需要await相应的函数即可,这种方式在语义化方面非常友好,对于代码的维护也很简单——只需要返回Promise并await它就好,无需像generator那般需要自己去维护内部yield的执行。

六、尾声

作为一个归纳和总结,本文内容的诸多知识点也是来自于他人的经验,不过加入了一些自己的理解和体会。不求科普于他人,但求作为个人的积累。希望读者可以提出订正的意见,共同学习进步!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325620810&siteId=291194637