资讯专栏INFORMATION COLUMN

谈一谈几种处理JavaScript异步操作的办法

曹金海 / 2838人阅读

摘要:问题是处理完了,却也引发了自己的一些思考处理的异步操作,都有一些什么方法呢一回调函数传说中的就是来自回调函数。而回调函数也是最基础最常用的处理异步操作的办法。

引言

js的异步操作,已经是一个老生常谈的话题,关于这个话题的文章随便google一下都可以看到一大堆。那么为什么我还要写这篇东西呢?在最近的工作中,为了编写一套相对比较复杂的插件,需要处理各种各样的异步操作。但是为了体积和兼容性,不打算引入任何的pollyfill,甚至连babel也不允许使用,这也意味着只能以es5的方式去处理。使用回调的方式对于解耦非常不利,于是找了别的方法去处理这个问题。问题是处理完了,却也引发了自己的一些思考:处理js的异步操作,都有一些什么方法呢?

一、回调函数

传说中的“callback hell”就是来自回调函数。而回调函数也是最基础最常用的处理js异步操作的办法。我们来看一个简单的例子:

首先定义三个函数:

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

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

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

其中fn2可以视作一个延迟了500毫秒执行的异步函数。现在我希望可以依次执行fn1fn2fn3。为了保证fn3在最后执行,我们可以把它作为fn2的回调函数:

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

fn2(fn3)

可以看到,fn2fn3完全耦合在一起,如果有多个类似的函数,很有可能会出现fn1(fn2(fn3(fn4(...))))这样的情况。回调地狱的坏处我就不赘述了,相信各位读者一定有自己的体会。

二、事件发布/订阅

发布/订阅模式也是诸多设计模式当中的一种,恰好这种方式可以在es5下相当优雅地处理异步操作。什么是发布/订阅呢?以上一节的例子来说,fn1fn2fn3都可以视作一个事件的发布者,只要执行它,就会发布一个事件。这个时候,我们可以通过一个事件的订阅者去批量订阅并处理这些事件,包括它们的先后顺序。下面我们基于上一章节的例子,增加一个消息订阅者的方法(为了简单起见,代码使用了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)

然后在fn1fn2fn3内调用其next()方法:

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

可以看到,函数的处理顺序等于传入AsyncFunArr的参数顺序。AsyncFunArr在内部维护一个数组,每一次调用next()方法都会按顺序推出数组所保存的一个对象并执行,这也是我在实际的工作中比较常用的方法。

三、Promise

使用Promise的方式,就不需要额外地编写一个消息订阅者函数了,只需要异步函数返回一个Promise即可。且看例子:

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")
}

同样的,我们定义了三个函数,其中fn2是一个返回Promise的异步函数,现在我们希望按顺序执行它们,只需要按以下方式即可:

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

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

使用Promise与回调有两个最大的不同,第一个是fn2fn3得以解耦;第二是把函数嵌套改为了链式调用,无论从语义还是写法上都对开发者更加友好。

四、generator

如果说Promise的使用能够化回调为链式,那么generator的办法则可以消灭那一大堆的Promise特征方法,比如一大堆的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

在这个例子中,generator函数asyncFunArr()接受一个待执行函数列表fn,异步函数将会通过yield来执行。在异步函数内,通过af.next()激活generator函数的下一步操作。

这么粗略的看起来,其实和发布/订阅模式非常相似,都是通过在异步函数内部主动调用方法,告诉订阅者去执行下一步操作。但是这种方式还是不够优雅,比如说如果有多个异步函数,那么这个generator函数肯定得改写,而且在语义化的程度来说也有一点不太直观。

五、优雅的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的执行。

六、尾声

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81762.html

相关文章

  • js技术 - 收藏集 - 掘金

    摘要:还记得刚开始学习的时候,内存管理前端掘金作为一门高级语言,并不像低级语言那样拥有对内存的完全掌控。第三方库的行代码内实现一个前端掘金前言本文会教你如何在行代码内,不依赖任何第三方的库,用纯实现一个。 (译) 如何使用 JavaScript 构建响应式引擎 —— Part 1:可观察的对象 - 掘金原文地址:How to build a reactive engine in JavaSc...

    Guakin_Huang 评论0 收藏0
  • js技术 - 收藏集 - 掘金

    摘要:还记得刚开始学习的时候,内存管理前端掘金作为一门高级语言,并不像低级语言那样拥有对内存的完全掌控。第三方库的行代码内实现一个前端掘金前言本文会教你如何在行代码内,不依赖任何第三方的库,用纯实现一个。 (译) 如何使用 JavaScript 构建响应式引擎 —— Part 1:可观察的对象 - 掘金原文地址:How to build a reactive engine in JavaSc...

    zhou_you 评论0 收藏0
  • 一谈javascript异步

    摘要:从今天开始研究一下的异步相关内容,感兴趣的请关注同期异步系列文章推荐异步中的回调异步与异步之异步之异步之和异步之一异步之二异步实战异步总结归档什么是异步我们知道的单线程的,这与它的用途有关。 从今天开始研究一下javascript的异步相关内容,感兴趣的请关注 同期异步系列文章推荐javascript异步中的回调javascript异步与promisejavascript异步之Prom...

    Sourcelink 评论0 收藏0
  • 一谈创建React Component几种方式

    摘要:用这种方式创建组件时,并没有对内部的函数,进行绑定,所以如果你想让函数在回调中保持正确的,就要手动对需要的函数进行绑定,如上面的,在构造函数中对进行了绑定。 当我们谈起React的时候,多半会将注意力集中在组件之上,思考如何将页面划分成一个个组件,以及如何编写可复用的组件。但对于接触React不久,还没有真正用它做一个完整项目的人来说,理解如何创建一个组件也并不那么简单。在最开始的时候...

    mylxsw 评论0 收藏0
  • 一谈Vuex

    摘要:是什么官方文档说道是一个专为应用程序开发的状态管理模式。触发之别名篇触发之对象展开运算符篇触发之对象展开运算符别名篇先引用官方文档的说法类似于,不同在于提交的是,而不是直接变更状态。 Vuex是什么 官方文档说道:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化 什么是状态管理模式...

    nifhlheimr 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<