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 相关文章推荐
window.open被浏览器拦截后的自定义提示效果代码
Nov 19 Javascript
关于jQuery中的end()使用方法
Jul 10 Javascript
jquery.messager.js插件导致页面抖动的解决方法
Jul 14 Javascript
禁用页面部分JavaScript不是全部而是部分
Sep 03 Javascript
简单实现node.js图片上传
Dec 18 Javascript
AngularJS实现表单验证功能
Jan 09 Javascript
javascript删除html标签函数cIsHTML
Jan 09 Javascript
利用Console来Debug的10个高级技巧汇总
Mar 26 Javascript
如何使node也支持从url加载一个module详解
Jun 05 Javascript
如何基于javascript实现贪吃蛇游戏
Feb 09 Javascript
javascript利用canvas实现鼠标拖拽功能
Jul 23 Javascript
详解Anyscript开发指南绕过typescript类型检查
Sep 23 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字符串函数学习之substr()
2015/03/27 PHP
深入理解PHP原理之执行周期分析
2016/06/01 PHP
php判断是否连接上网络的方法实例详解
2016/12/14 PHP
PHP实现的登录页面信息提示功能示例
2017/07/24 PHP
php实现将数据做成json的格式给前端使用
2018/08/21 PHP
PHP+mysql防止SQL注入的方法小结
2019/04/27 PHP
PHP论坛实现积分系统的思路代码详解
2020/06/01 PHP
Javascript writable特性介绍
2015/02/27 Javascript
jQuery实现文件上传进度条特效
2015/08/12 Javascript
JavaScript设计模式经典之工厂模式
2016/02/24 Javascript
Bootstrap表单布局
2016/07/19 Javascript
jQuery实现根据生日计算年龄 星座 生肖
2016/11/23 Javascript
jQuery实现的checkbox级联选择下拉菜单效果示例
2016/12/26 Javascript
js仿网易表单及时验证功能
2017/03/07 Javascript
深入解析js轮播插件核心代码的实现过程
2017/04/14 Javascript
实例学习JavaScript读取和写入cookie
2018/01/29 Javascript
微信小程序以7天为周期连续签到7天功能效果的示例代码
2020/08/20 Javascript
[01:18]PWL开团时刻DAY4——圣剑与抢盾
2020/11/03 DOTA
一个基于flask的web应用诞生 flask和mysql相连(4)
2017/04/11 Python
Python 实现简单的shell sed替换功能(实例讲解)
2017/09/29 Python
python使用Plotly绘图工具绘制散点图、线形图
2019/04/02 Python
python3 requests库文件上传与下载实现详解
2019/08/22 Python
Django实现简单网页弹出警告代码
2019/11/15 Python
python实现信号时域统计特征提取代码
2020/02/26 Python
Python文件操作基础流程解析
2020/03/19 Python
Python爬虫实现自动登录、签到功能的代码
2020/08/20 Python
纯CSS3发光分享按钮的实现教程
2014/09/06 HTML / CSS
三陽商会官方网站:Sanyo iStore
2019/05/15 全球购物
致裁判员加油稿
2014/02/08 职场文书
80后职场人的职业生涯规划
2014/03/08 职场文书
管理部副部长岗位职责范文
2014/03/09 职场文书
党的群众路线教育实践活动心得体会范文
2014/11/05 职场文书
学期个人自我总结
2015/02/13 职场文书
Python中rapidjson参数校验实现
2021/07/25 Python
MySQL多表查询机制
2022/03/17 MySQL
利用JuiceFS使MySQL 备份验证性能提升 10 倍
2022/03/17 MySQL