JavaScript异步操作的几种常见处理方法实例总结


Posted in Javascript onMay 11, 2020

本文实例讲述了JavaScript异步操作的几种常见处理方法。分享给大家供大家参考,具体如下:

引言

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的执行。

六、尾声

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

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun测试上述代码运行效果。

更多关于JavaScript相关内容可查看本站专题:《JavaScript操作DOM技巧总结》、《JavaScript页面元素操作技巧总结》、《JavaScript事件相关操作与技巧大全》、《JavaScript查找算法技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript错误与调试技巧总结》

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
JavaScript中的私有成员
Sep 18 Javascript
javascript拓展DOM操作 prependChild insertAfert
Nov 17 Javascript
javascript 判断中文字符长度的函数代码
Aug 27 Javascript
js字符串操作方法实例分析
May 06 Javascript
JavaScript实现简单的tab选项卡切换
Jan 05 Javascript
JavaScript每天必学之基础知识
Sep 17 Javascript
JS调用打印机功能简单示例
Nov 28 Javascript
详解Vue.js动态绑定class
Dec 20 Javascript
jQuery实现表格冻结顶栏效果
Aug 20 jQuery
谈谈vue中mixin的一点理解
Dec 12 Javascript
jquery实现左右轮播切换效果
Jan 01 jQuery
详解JS WebSocket断开原因和心跳机制
May 07 Javascript
Nuxt默认模板、默认布局和自定义错误页面的实现
May 11 #Javascript
Vue.js获取手机系统型号、版本、浏览器类型的示例代码
May 10 #Javascript
vue总线机制(bus)知识点详解
May 10 #Javascript
vue路由跳转传递参数的方式总结
May 10 #Javascript
javascript单张多张图无缝滚动实例代码
May 10 #Javascript
JavaScript面试中常考的字符串操作方法大全(包含ES6)
May 10 #Javascript
js实现文章目录索引导航(table of content)
May 10 #Javascript
You might like
深入浅析PHP无限极分类的案例教程
2016/05/09 PHP
PHP实现的服务器一致性hash分布算法示例
2018/08/09 PHP
javascript 遍历验证所有文本框的值
2009/08/27 Javascript
jquery 事件对象属性小结
2010/04/27 Javascript
基于jquery实现的可以编辑选择的下拉框的代码
2010/11/19 Javascript
JavaScript调试技巧之console.log()详解
2014/03/19 Javascript
jQuery+ajax中getJSON() 用法实例
2014/12/22 Javascript
JavaScript图片轮播代码分享
2015/07/31 Javascript
Bootstrap每天必学之弹出框(Popover)插件
2016/04/25 Javascript
基于jquery实现智能表单验证操作
2016/05/09 Javascript
微信小程序左右滑动的实现代码
2017/12/15 Javascript
vue移动UI框架滑动加载数据的方法
2018/03/12 Javascript
基于bootstrap页面渲染的问题解决方法
2018/08/09 Javascript
Node.js学习教程之Module模块
2019/09/03 Javascript
Python中的类学习笔记
2014/09/23 Python
python分析nignx访问日志脚本分享
2015/02/26 Python
python multiprocessing多进程变量共享与加锁的实现
2019/10/02 Python
Python多线程threading join和守护线程setDeamon原理详解
2020/03/18 Python
宝塔面板成功部署Django项目流程(图文)
2020/06/22 Python
Python错误的处理方法
2020/06/23 Python
python 如何停止一个死循环的线程
2020/11/24 Python
用CSS3实现背景渐变的方法
2015/07/14 HTML / CSS
H5新属性audio音频和video视频的控制详解(推荐)
2016/12/09 HTML / CSS
AVIS安飞士奥地利租车官网:提供奥地利、欧洲和全世界汽车租赁
2016/11/29 全球购物
有机童装:Toby Tiger
2018/05/23 全球购物
致跳远运动员广播稿
2014/02/11 职场文书
公开服务承诺制度
2014/03/26 职场文书
幼儿园师德演讲稿
2014/05/06 职场文书
个人考核材料
2014/05/15 职场文书
法人授权委托书
2014/09/16 职场文书
党员自我剖析材料范文
2014/10/06 职场文书
领导班子整改措施
2014/10/24 职场文书
2014年工作总结及2015工作计划
2014/12/12 职场文书
2015年社区宣传工作总结
2015/05/20 职场文书
故意伤害罪辩护词
2015/05/21 职场文书
张丽莉事迹观后感
2015/06/16 职场文书