Javascript异步编程的4种方法让你写出更出色的程序


Posted in Javascript onJanuary 17, 2013

你可能知道,Javascript语言的执行环境是"单线程"(single thread)。
所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
Javascript异步编程的4种方法让你写出更出色的程序 
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)

"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
Javascript异步编程的4种方法让你写出更出色的程序 
"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

本文总结了"异步模式"编程的4种方法,理解它们可以让你写出结构更合理、性能更出色、维护更方便的Javascript程序。

一、回调函数
这是异步编程最基本的方法。
假定有两个函数f1和f2,后者等待前者的执行结果。

f1(); 
f2();

如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。
function f1(callback){ 

setTimeout(function () { 



// f1的任务代码 



callback(); 


}, 1000); 

}

执行代码就变成下面这样:
f1(f2);

采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。

回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。

二、事件监听
另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
还是以f1和f2为例。首先,为f1绑定一个事件(这里采用的jQuery的写法)。

f1.on('done', f2);

上面这行代码的意思是,当f1发生done事件,就执行f2。然后,对f1进行改写:
function f1(){ 

setTimeout(function () { 



// f1的任务代码 



f1.trigger('done'); 


}, 1000); 

}

f1.trigger('done')表示,执行完成后,立即触发done事件,从而开始执行f2。
这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。
三、发布/订阅
上一节的"事件",完全可以理解成"信号"。
我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。

这个模式有多种实现,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。
首先,f2向"信号中心"jQuery订阅"done"信号。

jQuery.subscribe("done", f2);

然后,f1进行如下改写:
function f1(){ 

setTimeout(function () { 



// f1的任务代码 



jQuery.publish("done"); 


}, 1000); 

}

jQuery.publish("done")的意思是,f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。
此外,f2完成执行后,也可以取消订阅(unsubscribe)。
jQuery.unsubscribe("done", f2);

这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

四、Promises对象
Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。
简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:

f1().then(f2);

f1要进行如下改写(这里使用的是jQuery的实现):
function f1(){ 

var dfd = $.Deferred(); 


setTimeout(function () { 



// f1的任务代码 



dfd.resolve(); 


}, 500); 


return dfd.promise; 

}

这样写的优点在于,回调函数变成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现许多强大的功能。

比如,指定多个回调函数:

f1().then(f2).then(f3);

再比如,指定发生错误时的回调函数:
f1().then(f2).fail(f3);

而且,它还有一个前面三种方法都没有的好处:如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,你不用担心是否错过了某个事件或信号。这种方法的缺点就是编写和理解,都相对比较难。
Javascript 相关文章推荐
javascript动画效果类封装代码
Aug 28 Javascript
基于jquery的自定义鼠标提示效果 jquery.toolTip
Nov 14 Javascript
JavaScript cookie的设置获取删除详解
Feb 11 Javascript
textarea焦点的用法实现获取焦点清空失去焦点提示效果
May 19 Javascript
通过BootStrap实现轮播图的实际应用
Sep 26 Javascript
javascript replace()第二个参数为函数时的参数用法
Dec 26 Javascript
JavaScript学习总结之正则的元字符和一些简单的应用
Jun 30 Javascript
JS实现移动端整屏滑动的实例代码
Nov 10 Javascript
Webpack 4如何动态切割JS注入文件名详解
Jul 09 Javascript
微信小程序模板消息限制实现无限制主动推送的示例代码
Aug 27 Javascript
vue学习笔记之slot插槽基本用法实例分析
Feb 01 Javascript
node中使用shell脚本的方法步骤
Mar 23 Javascript
jQuery链式操作如何实现以及为什么要用链式操作
Jan 17 #Javascript
JQuery中根据属性或属性值获得元素(6种情况获取方法)
Jan 17 #Javascript
JavaScript控制Session操作方法
Jan 17 #Javascript
file模式访问网页时iframe高度自适应解决方案
Jan 16 #Javascript
jquery如何改变html标签的样式(两种实现方法)
Jan 16 #Javascript
jquery选择器的选择使用及性能介绍
Jan 16 #Javascript
jQuery旋转插件—rotate支持(ie/Firefox/SafariOpera/Chrome)
Jan 16 #Javascript
You might like
mysql中存储过程、函数的一些问题
2007/02/14 PHP
php从csv文件读取数据并输出到网页的方法
2015/03/14 PHP
yii实现使用CUploadedFile上传文件的方法
2015/12/28 PHP
IE图片缓存document.execCommand("BackgroundImageCache",false,true)
2011/03/01 Javascript
jQuery实现滑动页面固定顶部显示(可根据显示位置消失与替换)
2015/10/28 Javascript
jQuery设置单选按钮radio选中/不可用的实例代码
2016/06/24 Javascript
简单的js表格操作
2016/09/24 Javascript
探究Vue.js 2.0新增的虚拟DOM
2016/10/20 Javascript
在html中引入外部js文件,并调用带参函数的方法
2016/10/31 Javascript
jQuery用FormData实现文件上传的方法
2016/11/21 Javascript
原生js实现可拖动的登录框效果
2017/01/21 Javascript
bootstrap插件treeview实现全选父节点下所有子节点和反选功能
2017/07/21 Javascript
分析javascript中9 个常见错误阻碍你进步
2017/09/18 Javascript
vue router下的html5 history在iis服务器上的设置方法
2017/10/18 Javascript
详解React之key的使用和实践
2018/09/29 Javascript
详解angular2如何手动点击特定元素上的点击事件
2018/10/16 Javascript
vue-cli系列之vue-cli-service整体架构浅析
2019/01/14 Javascript
python re正则表达式模块(Regular Expression)
2014/07/16 Python
在Python中使用判断语句和循环的教程
2015/04/25 Python
正确理解python中的关键字“with”与上下文管理器
2017/04/21 Python
python读写csv文件实例代码
2019/07/05 Python
解决Pytorch 训练与测试时爆显存(out of memory)的问题
2019/08/20 Python
django-xadmin根据当前登录用户动态设置表单字段默认值方式
2020/03/13 Python
python实现梯度下降算法的实例详解
2020/08/17 Python
Pycharm配置autopep8实现流程解析
2020/11/28 Python
HTML5调用手机发短信和打电话功能
2020/04/29 HTML / CSS
Abe’s of Maine:自1979以来销售相机和电子产品
2016/11/21 全球购物
Java里面如何把一个Array数组转换成Collection, List
2013/07/26 面试题
初始化了一个没有run()方法的线程类,是否会出错?
2014/03/27 面试题
护理专业个人求职简历的自我评价
2013/10/13 职场文书
运动会通讯稿400字
2014/01/28 职场文书
机械制造专业毕业生求职信
2014/03/02 职场文书
解除劳动合同协议书范本
2014/04/14 职场文书
教师群众路线教育实践活动学习笔记
2014/11/05 职场文书
小程序wx.getUserProfile接口的具体使用
2021/06/02 Javascript
JavaScript 定时器详情
2021/11/11 Javascript