延期(deferred)的承诺(promise) — jq异步编程浅析
引子
相信各位developers对js中的异步概念不会陌生,异步操作后的逻辑由回调函数来执行,回调函数(callback function)顾名思义就是“回头调用的函数”,函数体事先已定义好,在未来的某个时候由某个事件触发调用,而这个时机,是程序本身无法控制的。
举几个常见例子:
事件绑定
动画
Ajax
上面的例子简单、典型,易于阅读和理解。
为了引出本文的主题,假设现在有3个ajax异步操作,分别为A、B、C,每个都封装成了函数,并可传入success回调作为参数。
请考虑以下场景:
希望这3个异步操作按顺序执行,形成执行队列,即A执行完了,B执行;B执行完了,C执行;
希望A,B完成了,再执行C;
对于场景(1),代码大概会是这样:
按传统的写法,自然而然会形成回调函数的多层嵌套,如果代码量比较多的话,代码的可读性和维护性会比较差。
对于场景(2),代码可能要复杂些了:
a) A、B操作分别需要有状态变量
isADone
,isBDone
,默认值均为false
;A成功后isADone
置为true
,B成功后isBDone
置为true
;b) 需要一段代码来反复探测上述2个状态变量,我们把这个行为称为pending,等pending到2个状态都为
true
时则执行C,并终止pending;
就本场景而言,上述方案还可勉强接受,如果情况再复杂些,代码的可读性和维护性也会大打折扣。
以上2种场景,只限定了3个异步操作的执行关系,实际的开发场景可能要比这个复杂的多,为了开发者能够更优雅的处理js异步操作,jq引入了Deferred
对象($.Deferred
),我们先来了解下Deferred
对象,然后看看它能为我们的异步编程带来哪些益处。
Deferred特性介绍
先创建一个Deferred
实例。
new
关键字是可选的,可以不写,这是因为在Deferred
构造函数中处理了,但不代表用原生的js基于构造函数创建对象不用写new
。
deferred
带有3种状态:pending(待定)、resolved(成功)、rejected(失败)。
deferred
的状态可以通过api进行切换,但不可逆。
可从英文字面意思理解:resolve(解决)后成功,reject(拒绝)后失败。
状态不可逆,是指一旦从待定状态切换到任何一个确定状态后,再次调用resolve
或reject
对原状态将不起任何作用。
deferred
通过语义对应的api来绑定不同状态时执行的回调函数。
resolve: done, always
reject: fail, always
其中always
绑定的回调函数,不论deferred
的状态是成功或失败,总会执行。
deferred
还有一个then
方法,来简化done
、fail
的写法:
另外,值得注意的是,当deferred
状态切换后(调用reject
或者resolve
后),再进行回调函数的绑定,那么对应状态的回调函数会立即执行。
举个例子:
deferred
还可以返回自己的操作子集。
promise
只有绑定回调的api,而没有状态切换的api。
deferred: reject, resolve, done, fail, then, always
promise: done, fail, then, always
很明显promise
是用来开放给外部调用的,而deferred
通常用于模块内部,来控制自身状态的切换。
举个直观的例子,一般在js中我们会用setTimeout
来做延时执行,如:
我们用deferred
来封装下,代码如下:
wait
函数内部,通过deferred
来进行状态切换,返回promise
对象,这样就可以在wait
外部进行回调函数的绑定。
其实该原理在jq中有个非常典型的案例,那就是$.ajax
方法,该方法会返回一个promise
,而方法内部有个deferred
用于决定自身状态,所以相对传统的ajax写法,我们也可以这么写:
其中done
,fail
等方法还可以接受多个callback、或者以callback array作为参数。
假定有个ajax请求,成功后需要对返回结果进行3个独立的处理,分别对应3个函数,我们可以这么写:
这样获取数据的逻辑和处理数据的逻辑进行了分离,数据处理不会全都堆在success回调函数中,代码整体看起来就更简洁易读。
回到之前的问题
再了解了Deferred
特性和简单应用后,我们再回头考虑前面提到的2个场景,是否能够用更好的方案来实现呢?答案是肯定的。
我们先看场景(2):A、B完成了,再执行C。
为什么先看场景(2),因为jq中正好有个现成的api:$.when
,能够很方便的满足该需求。
$.when
可接受多个deferred(promise)
对象,$.when
会返回一个promise,用作后续回调的绑定,官网示例如下:
当$.when
中2个异步请求均返回成功后,即会执行myFunc回调;
当$.when
中只要有一个异步请求失败,即会执行myFailure回调;
解决方案已经很清晰了,我们稍加改造之前的asyncA
、asyncB
方法,让它们分别返回各自的promise
,然后直接使用$.when
即可。
我可以先用ajax来做代码示例:
下面再来个直接使用deferred
的代码实例:
整体上,是否感觉代码更清晰,更利于理解了呢?
我们再来看场景(1):A执行完了,B执行;B执行完了,C执行。
要完成这个实现的改造,需要深入了解下deferred
对象的then
方法。上面我们介绍了then
的一般用法:
用于done
,fail
的简化写法。但它还有一个非常重要的作用,可以用来传递deferred(promise)
对象,实现任务链条(chain tasks)。
下面就用then
来实现场景(1)的需求:
是不是感觉很简单,如果C后面还有D,那继续往下then
就可以了。
假定asyncA
是一个ajax操作,其中回调函数data参数,即为ajax成功后,后台返回的数据。
值得注意的是,如果用then
方法进行deferred
对象的传递,回调函数必须return一个deferred
。
上述的例子还有一个特点:由于then
的第一个参数是deferred
对象成功时执行的回调,若deferred
状态切换到失败,则后续then
的成功回调将不再执行,任务链就中断了。
该场景有一定的实践价值,比如一个业务网站,页面上有多个展示模块都是通过ajax问后台拿数据的,如果页面一进来,同时向后台发好几个ajax请求,会瞬间增加后台IO的压力,可能会增加用户等待界面反馈数据的时间,造成体验下降。在这种情况下,把ajax请求作为队列处理是比较合适的,可按重要性逐步请求获取数据,提高性能和渲染体验。
小结
本文只是初步的介绍了下jq中的Deferred
,并没有深入到每一个细节,但它的基本功用应该是覆盖到了,感兴趣的同学可以前往jq官网,自行研究下。
其实promise
就可以按照字面理解为“承诺”,可以把deferred
理解成你的一个手下,你让他去跟进一件事,而这个事情什么时候可以有个结果,你并不清楚,但他会给你承诺,将按照你的意图:事情若这样,后续要做什么;事情若那样,后续要做什么。
延期(deferred)的承诺(promise) — jq异步编程浅析的更多相关文章
- Promise和异步编程
前面的话 JS有很多强大的功能,其中一个是它可以轻松地搞定异步编程.作为一门为Web而生的语言,它从一开始就需要能够响应异步的用户交互,如点击和按键操作等.Node.js用回调函数代替了事件,使异步编 ...
- Promise对象 异步编程
Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大.所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是 ...
- 《深入理解ES6》笔记—— Promise与异步编程(11)
为什么要异步编程 我们在写前端代码时,经常会对dom做事件处理操作,比如点击.激活焦点.失去焦点等:再比如我们用ajax请求数据,使用回调函数获取返回值.这些都属于异步编程. 也许你已经大概知道Jav ...
- Promise对异步编程的贡献以及基本API了解
异步: 核心: 现在运行的部分和将来运行的部分之间的关系 常用方案: 从现在到将来的等待,通常使用一个回调函数在结果返回时得到结果 控制台(因为console族是由宿主环境即游览器实现的)可能会使用异 ...
- 学习Promise异步编程
JavaScript引擎建立在单线程事件循环的概念上.单线程( Single-threaded )意味着同一时刻只能执行一段代码.所以引擎无须留意那些"可能"运行的代码.代码会被放 ...
- 异步编程——promise
异步编程--promise 定义 Promise是异步编程的一个解决方案,相比传统的解决方法--回调函数,使用Promise更为合理和强大,避免了回调函数之间的层层嵌套,也使得代码结构更为清晰,便于维 ...
- 异步编程:When.js快速上手
前些天我在团内做了一个关于AngularJS的分享.由于AngularJS大量使用Promise,所以我把基于Promise的异步编程也一并介绍了下.很多东西都是一带而过,这里再记录下. Angula ...
- 异步编程when.js
when.js很小,压缩后只有数kb,gzip后的大小几乎可以忽略.在Node和浏览器环境里都可以使用when.js 首先,我们看一小段代码: var getData = function(callb ...
- JavaScript异步编程的主要解决方案—对不起,我和你不在同一个频率上
众所周知(这也忒夸张了吧?),Javascript通过事件驱动机制,在单线程模型下,以异步的形式来实现非阻塞的IO操作.这种模式使得JavaScript在处理事务时非常高效,但这带来了很多问题,比如异 ...
随机推荐
- C 字符/字符串常用函数
string.h中常用函数 char * strchr(char * str ,char ch); 从字符串str中查找首次出现字符ch的位置,若存在返回查找后的地址,若不存在则返回NULL void ...
- css3的3D翻牌效果
利用hover控制动画,一个从翻面向正面翻,一个从正面向反面翻. 利用:backface-visibility: hidden;控制反面的元素不显示. 不支持的浏览器直接切换层级换图. <!DO ...
- ACE_linux:读写锁
1.涉及类 ACE_RW_Thread_Mutex //ACE读写锁ACE_Read_Guard //ACE加读锁ACE_Write_Guard //ACE加写锁ACE_Thread_Manager ...
- javascript操作html元素CSS属性
下面先记录一下JS控制CSS所使用的方法. 1.使用javascript更改某个css class的属性... <style type="text/css"> .ori ...
- Centos6.5下rsync文件同步安装配置及遇到的问题解决方法
实验节点如下: 源节点:192.168.0.111 备份节点:192.168.0.112 ------------------------------以下部分在两个节点上执行1.防火墙开放873端口( ...
- MVC 中 使用TagBuilder扩展HtmlHelper
TagBuilder就是标签建造器,我们就用它来建造属于我们自己标签生成函数. 无废话,直接上代码 例: using System.Web.Mvc; public static class HtmlE ...
- ToString()字符转换类型
100.ToString("n");结果是100.00 100.ToString("c");结果是¥100.00 100.ToString("e&qu ...
- shell写的计算器
#!/bin/bashif [ $# -ne 3 ] then echo "Usage: $0 num1 + num2" fi case $2 in +) echo $1$2$3= ...
- Lucene基础(三)-- 中文分词及高亮显示
Lucene分词器及高亮 分词器 在lucene中我们按照分词方式把文档进行索引,不同的分词器索引的效果不太一样,之前的例子使用的都是标准分词器,对于英文的效果很好,但是中文分词效果就不怎么样,他会按 ...
- entity framework in mysql
To start using Entity Framework 6 and Visual Studio 2013 is necessary to install MySQL for Visual St ...