本文实例讲述了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毫秒执行的异步函数。现在我希望可以依次执行fn1
,fn2
,fn3
。为了保证fn3
在最后执行,我们可以把它作为fn2
的回调函数:
function fn2 (f) { setTimeout(() => { console.log('Function 2') f() }, 500) } fn2(fn3)
可以看到,fn2
和fn3
完全耦合在一起,如果有多个类似的函数,很有可能会出现fn1(fn2(fn3(fn4(...))))
这样的情况。回调地狱的坏处我就不赘述了,相信各位读者一定有自己的体会。
二、事件发布/订阅
发布/订阅模式也是诸多设计模式当中的一种,恰好这种方式可以在es5下相当优雅地处理异步操作。什么是发布/订阅呢?以上一节的例子来说,fn1
,fn2
,fn3
都可以视作一个事件的发布者,只要执行它,就会发布一个事件。这个时候,我们可以通过一个事件的订阅者去批量订阅并处理这些事件,包括它们的先后顺序。下面我们基于上一章节的例子,增加一个消息订阅者的方法(为了简单起见,代码使用了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)
然后在fn1
,fn2
,fn3
内调用其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与回调有两个最大的不同,第一个是fn2
与fn3
得以解耦;第二是把函数嵌套改为了链式调用,无论从语义还是写法上都对开发者更加友好。
四、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.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容可查看本站专题:《JavaScript操作DOM技巧总结》、《JavaScript页面元素操作技巧总结》、《JavaScript事件相关操作与技巧大全》、《JavaScript查找算法技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript错误与调试技巧总结》
希望本文所述对大家JavaScript程序设计有所帮助。
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。