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方法和技巧大全
Dec 27 Javascript
function, new function, new Function之间的区别
Mar 08 Javascript
给Flash加一个超链接(推荐使用透明层)兼容主流浏览器
Jun 09 Javascript
用Js实现的动态增加表格示例自己写的
Oct 21 Javascript
采用call方式实现js继承
May 20 Javascript
js本地图片预览实现代码
Oct 09 Javascript
JavaScript实现职责链模式概述
Jan 25 Javascript
原生JS实现的轮播图功能详解
Aug 06 Javascript
微信小程序 MinUI组件库系列之badge徽章组件示例
Aug 20 Javascript
layer.confirm点击第一个按钮关闭弹出框的方法
Sep 09 Javascript
vue 实现通过vuex 存储值 在不同界面使用
Nov 11 Javascript
JS数组方法concat()用法实例分析
Jan 18 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通过function_exists检测函数是否存在的方法
2015/03/18 PHP
Javascript技巧之不要用for in语句对数组进行遍历
2010/10/20 Javascript
初窥JQuery(二)事件机制(2)
2010/12/06 Javascript
打印json对象的内容及JSON.stringify函数应用
2013/03/29 Javascript
js判断屏幕分辨率的代码
2013/07/16 Javascript
JS小功能(列表页面隔行变色)简单实现
2013/11/28 Javascript
基于JavaScript实现移除(删除)数组中指定元素
2016/01/04 Javascript
JS生成不重复的随机数组的简单实例
2016/07/10 Javascript
关于jQuery EasyUI 中刷新Tab选项卡后一个页面变形的解决方法
2017/03/02 Javascript
Underscore之Array_动力节点Java学院整理
2017/07/10 Javascript
如何用input标签和jquery实现多图片的上传和回显功能
2018/05/16 jQuery
浅谈angular2子组件的事件传递(任意组件事件传递)
2018/09/30 Javascript
微信小程序自定义可滑动顶部TabBar选项卡实现页面切换功能示例
2019/05/14 Javascript
解决Vue项目打包后打开index.html页面显示空白以及图片路径错误的问题
2019/10/25 Javascript
js实现石头剪刀布游戏
2020/10/11 Javascript
[52:27]2018DOTA2亚洲邀请赛 3.31 小组赛B组 paiN vs Secret
2018/04/01 DOTA
Python yield 小结和实例
2014/04/25 Python
使用简单工厂模式来进行Python的设计模式编程
2016/03/01 Python
Python基于正则表达式实现检查文件内容的方法【文件检索】
2017/08/30 Python
python中单下划线_的常见用法总结
2018/07/10 Python
对python捕获ctrl+c手工中断程序的两种方法详解
2018/12/26 Python
python中的句柄操作的方法示例
2019/06/20 Python
django 实现将本地图片存入数据库,并能显示在web上的示例
2019/08/07 Python
tensorflow之变量初始化(tf.Variable)使用详解
2020/02/06 Python
keras自定义回调函数查看训练的loss和accuracy方式
2020/05/23 Python
香港最新科技与优质家居产品购物网站:J SELECT
2018/08/21 全球购物
环保专业大学生职业规划设计
2014/01/10 职场文书
竞选班干部演讲稿300字
2014/08/20 职场文书
学校感恩节活动策划方案
2014/10/06 职场文书
有限公司股东合作协议书
2014/10/29 职场文书
感恩母亲节活动总结
2015/02/10 职场文书
女方离婚起诉书
2015/05/18 职场文书
2015秋季开学典礼演讲稿
2015/07/16 职场文书
go语言中切片与内存复制 memcpy 的实现操作
2021/04/27 Golang
如何解决.cuda()加载用时很长的问题
2021/05/24 Python
SQL 尚未定义空闲 CPU 条件 - OnIdle 作业计划将不起任何作用
2021/06/30 SQL Server