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 相关文章推荐
我见过最全的个人js加解密功能页面
Dec 12 Javascript
window.js 主要包含了页面的一些操作
Dec 23 Javascript
Javascript中Event属性搜集整理
Sep 17 Javascript
IE的事件传递-event.cancelBubble示例介绍
Jan 12 Javascript
jQuery简单实现日历的方法
May 04 Javascript
JavaScript实现把数字转换成中文
Jun 29 Javascript
浅析C/C++,Java,PHP,JavaScript,Json数组、对象赋值时最后一个元素后面是否可以带逗号
Mar 22 Javascript
jQuery的内容过滤选择器学习教程
Apr 18 Javascript
js和jQuery设置Opacity半透明 兼容IE6
May 24 Javascript
源码分析Vue.js的监听实现教程
Apr 23 Javascript
动手写一个angular版本的Message组件的方法
Dec 16 Javascript
layer.alert回调函数执行关闭弹窗的实例
Sep 11 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处理JSON字符串key缺少双引号的解决方法
2014/09/16 PHP
PHP手机短信验证码实现流程详解
2018/05/17 PHP
PHP实现基于3DES算法加密解密字符串示例
2018/08/24 PHP
JS window.opener返回父页面的应用
2009/10/24 Javascript
Javascript结合css实现网页换肤功能
2009/11/02 Javascript
Javascript 网页黑白效果实现代码(兼容IE/FF等)
2010/04/23 Javascript
js+css实现增加表单可用性之提示文字
2013/06/03 Javascript
js点击更换背景颜色或图片的实例代码
2013/06/25 Javascript
仿淘宝TAB切换搜索框搜索切换的相关内容
2014/09/21 Javascript
浅析js预加载/延迟加载
2014/09/25 Javascript
JavaScript实现查找字符串中第一个不重复的字符
2014/12/29 Javascript
JS实现点击按钮后框架内载入不同网页的方法
2015/05/05 Javascript
5种JavaScript脚本加载的方式
2017/01/16 Javascript
es6新特性之 class 基本用法解析
2018/05/05 Javascript
在Node.js中将SVG图像转换为PNG,JPEG,TIFF,WEBP和HEIF格式的方法
2019/08/22 Javascript
Vue3.0 响应式系统源码逐行分析讲解
2019/10/14 Javascript
vue实现pdf文档在线预览功能
2019/11/26 Javascript
小程序跨页面交互的作用与方法详解
2020/01/07 Javascript
Vue项目中使用flow做类型检测的方法
2020/03/18 Javascript
javascript+css实现俄罗斯方块小游戏
2020/06/28 Javascript
动态实现element ui的el-table某列数据不同样式的示例
2021/01/22 Javascript
[01:25]2014DOTA2国际邀请赛 zhou分析LGD比赛情况
2014/07/14 DOTA
在Python中使用Mako模版库的简单教程
2015/04/08 Python
Python批量修改文本文件内容的方法
2016/04/29 Python
python 请求服务器的实现代码(http请求和https请求)
2018/05/25 Python
Python如何发布程序的详细教程
2018/10/09 Python
解决python xlrd无法读取excel文件的问题
2018/12/25 Python
对Python3 pyc 文件的使用详解
2019/02/16 Python
Python datetime 如何处理时区信息
2020/09/02 Python
python 偷懒技巧——使用 keyboard 录制键盘事件
2020/09/21 Python
KARATOV珠宝在线商店:俄罗斯珠宝品牌
2019/03/13 全球购物
幼儿园庆六一活动方案
2014/03/06 职场文书
2014年作风建设心得体会
2014/10/22 职场文书
大学生各类奖学金申请书
2019/06/24 职场文书
python 破解加密zip文件的密码
2021/04/22 Python
python代码实现扫码关注公众号登录的实战
2021/11/01 Python