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 相关文章推荐
jquery 弹出层实现代码
Oct 30 Javascript
Javascript面向对象编程
Mar 18 Javascript
node.js入门教程迷你书、node.js入门web应用开发完全示例
Apr 06 Javascript
关闭浏览器窗口弹出提示框并且可以控制其失效
Apr 15 Javascript
js实现a标签超链接提交form表单的方法
Jun 24 Javascript
javascript实现任务栏消息提示的简单实例
May 31 Javascript
Node.js的环境安装配置(使用nvm方式)
Oct 11 Javascript
利用JS轻松实现获取表单数据
Dec 06 Javascript
详解JavaScript对象的深浅复制
Mar 30 Javascript
详解webpack3编译兼容IE8的正确姿势
Dec 21 Javascript
jquery获取元素到屏幕四周可视距离的方法
Sep 05 jQuery
解析原生JS getComputedStyle
May 25 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中mysql连接和基本操作代码(快速测试使用,简单方便)
2014/04/25 PHP
PHP5.3与5.5废弃与过期函数整理汇总
2014/07/10 PHP
PHP制作万年历
2015/01/07 PHP
PHP生成随机码的思路与方法实例探索
2019/04/11 PHP
javascript 一个自定义长度的文本自动换行的函数
2007/08/19 Javascript
javascript实现的距离现在多长时间后的一个格式化的日期
2009/10/29 Javascript
javascript权威指南 学习笔记之javascript数据类型
2011/09/24 Javascript
jquery实现个人中心导航菜单效果和美观都非常不错
2014/09/02 Javascript
JQuery的Ajax中Post方法传递中文出现乱码的解决方法
2014/10/21 Javascript
javascript判断变量是否有值的方法
2015/04/20 Javascript
解决jquery插件:TypeError:$.browser is undefined报错的方法
2015/11/21 Javascript
利用python分析access日志的方法
2016/10/26 Javascript
浅析javascript中的Event事件
2016/12/09 Javascript
Vue.js鼠标悬浮更换图片功能
2017/05/17 Javascript
使用mint-ui实现省市区三级联动效果的示例代码
2018/02/09 Javascript
浅谈Vue下使用百度地图的简易方法
2018/03/23 Javascript
JS基于封装函数实现的表格分页完整示例
2018/06/26 Javascript
React key值的作用和使用详解
2018/08/23 Javascript
vue-cli项目使用mock数据的方法(借助express)
2019/04/15 Javascript
python实现将元祖转换成数组的方法
2015/05/04 Python
浅谈python为什么不需要三目运算符和switch
2016/06/17 Python
python opencv读mp4视频的实例
2018/12/07 Python
Python3实现的判断环形链表算法示例
2019/03/07 Python
Python 旋转打印各种矩形的方法
2019/07/09 Python
Python实现Selenium自动化Page模式
2019/07/14 Python
HTML5之HTML元素扩展(下)—增强的Form表单元素值得关注
2013/01/31 HTML / CSS
Java Servlet API中forward() 与redirect()的区别
2014/04/20 面试题
展会邀请函范文
2014/01/26 职场文书
公共艺术专业自荐信
2014/09/01 职场文书
简易离婚协议书(范本)
2014/10/25 职场文书
先进工作者事迹材料
2014/12/23 职场文书
单位工资证明范本
2015/06/12 职场文书
2015大学生入党个人自传
2015/06/26 职场文书
聘任书范文大全
2015/09/21 职场文书
vue实现无缝轮播效果(跑马灯)
2021/05/14 Vue.js
java调用Restful接口的三种方法
2021/08/23 Java/Android