对于$.ajax请求来说,如果层级比较多,程序看起来会比较乱,而为了解决这种问题,才有了$when...done...fail...then的封装,它将$.ajax这嵌套结构转成了顺序平行的结果,向下面的$.ajax写法,看起来很乱
- $.ajax({
- url: "/home/GetProduct",
- dataType: "JSON",
- type: "GET",
- success: function (data) {
- $.ajax({
- url: "/home/GetProduct",
- dataType: "JSON",
- type: "GET",
- success: function (data) {
- $.ajax({
- url: "/home/GetProduct",
- dataType: "JSON",
- type: "GET",
- success: function (data) {
- }
- }
- }
- $.when($.ajax({
- url: "/home/GetProduct",
- dataType: "JSON",
- type: "GET",
- success: function (data) {
- alert(JSON.stringify(data));
- }
- })).done(function (data) {
- alert(data[0].Name);
- }).done(function (data) {
- alert(data[1].Name);
- }).fail(function () {
- alert("程序出现错误!");
- }).then(function (data) {
- alert("程序执行完成");
- });
下面通过一个例子再给大家介绍jquery when then(done) 用法
//运行条件jquery 1.82以上,直接运行代码,看结果
- var log = function(msg){
- window.console && console.log(msg)
- }
- function asyncThing1(){
- var dfd = $.Deferred();
- setTimeout(function(){
- log('asyncThing1 seems to be done...');
- dfd.resolve('1111');
- },1000);
- return dfd.promise();
- }
- function asyncThing2(){
- var dfd = $.Deferred();
- setTimeout(function(){
- log('asyncThing2 seems to be done...');
- dfd.resolve('222');
- },1500);
- return dfd.promise();
- }
- function asyncThing3(){
- var dfd = $.Deferred();
- setTimeout(function(){
- log('asyncThing3 seems to be done...');
- dfd.resolve('333');
- },2000);
- return dfd.promise();
- }
- /* do it */
- $.when( asyncThing1(), asyncThing2(), asyncThing3() ).done(function(res1, res2, res3){
- log('all done!');
- log(res1 + ', ' + res2 + ', ' + res3);
- })
如果AJAX请求之间存在依赖关系,我们的代码就会形成Pyramid of Doom(金字塔厄运)。比如我们要完成这样一件事:有4个供Ajax访问的url地址,需要先Ajax访问第1个,在第1个访问完成后,用拿到的返回数据作为参数再访问第2个,第2个访问完成后再第3个...以此到4个全部访问完成。按照这样的写法,似乎会变成这样:
- $.ajax({
- url: url1,
- success: function(data){
- $.ajax({
- url: url2,
- data: data,
- success: function(data){
- $.ajax({
- //...
- });
- }
- });
- }
- });
- function success(data)
- {
- alert(success data = + data);
- }
- function fail(data)
- {
- alert(fail data = + data);
- }
- function progress(data)
- {
- alert(progress data = + data);
- }
- var deferred = $.Deferred();
- // 一起注册回调
- deferred.then(success, fail, progress);
- // 分别注册回调
- deferred.done(success);
- deferred.fail(fail);
- deferred.progress(progress);
- deferred.notify(10%);
- deferred.resolve(ok);
2.Deferred.then()解决多个异步操作之间有依赖的问题,这才是then()真正有意义的场景。JQuery1.8之后,then()取代了过时的pipe()方法。这种场景下,我们需要使用Deferred.then()返回的新Promise对象。上面的第一种使用方式,我们忽略了Deferred.then()的返回值。
- var deferred = $.Deferred();
- // 使用then()注册一个resolved状态的回调函数,并返回一个过滤后的promise
- // 返回的filtered已经不是原来的Deferred或者Promise对象了
- var filtered = deferred.then(function( value ) {
- alert(trigger Deferred filter.value=+value);//5
- return value * 2;
- });
- // 用过滤后的Promise再次注册回调函数
- filtered.done(function( value ) {
- alert(filtered value= + value);//10
- });
- deferred.resolve( 5 );
我们知道deferred.resolve()、deferred.reject()、deferred.notify()可以指定参数值,这个参数会传递给相应状态下的回调函数。如果我们使用的是done()、fail()、progress()注册的回调函数,那么某个状态下的所有回调函数得到的都是相同参数。但是如果我们使用了then()注册回调函数,那么第一回调函数的返回值将作为第二个回调函数的参数,同样的第二个函数的返回值是第三个回调函数的参数。可以对比下面的2段代码,体会下done()和then的差别。
- var deferred = $.Deferred();
- // done()返回的仍然是原来的Deferred对象
- var done_ret = deferred.done(function(data){
- alert(data=+data);//5
- return 2 * data;
- });
- alert(deferred == done_ret);//true
- done_ret.done(function(data){
- alert(data=+data);//5
- });
- deferred.resolve( 5 );
- var deferred = $.Deferred();
- // then()返回的是一个新Promise对象
- //then注册的回调函数的返回值将作为这个新Promise的参数
- var then_ret = deferred.then(function(data){
- alert(data=+data);//5
- return 2 * data;
- });
- alert(then_ret == deferred);//false
- then_ret.done(function(data){
- alert(data=+data);//10
- });
- deferred.resolve( 5 );
- var defer = $.Deferred();
- var filtered = defer.then( null, function( value ) {
- return value * 3;
- });
- defer.reject( 6 );
- filtered.fail(function( value ) {
- alert( Value is ( 3*6 = ) 18: + value );
- });
- var defered = $.Deferred();
- var promise1 = defered.then(function(data){
- alert(data);//
- return data+=1;
- });
- var promise2 = promise1.then(function(data){
- alert(data);//1
- return data+=2;
- });
- var promise3 = promise2.then(function(data){
- alert(data);//12
- return data+=3;
- });
- promise3.done(function(data){
- alert(data);//123
- });
- defered.resolve();
- var promise1 = $.ajax(url1);
- var promise2 = promise1.then(function(data){
- return $.ajax(url2, { data: data });
- });
- var promise3 = promise2.then(function(data){
- return $.ajax(url3, { data: data });
- });
- promise3.done(function(data){
- // data retrieved from url3
- });
二、示例
以前写动画时,我们通常是这么干的:
$('.animateEle').animate({
opacity:'.5'
}, 4000,function(){
$('.animateEle2').animate({
width:'100px'
},2000,function(){
// 这样太伤了
$('.animateEle3').animate({
height:'0'
},2000);
});
});
假如这么使用回调的话,那就太伤了。幸好,还有一些现成的 Promise
解决方案来优雅地解决这种问题。
我们看看 jQuery
提供的解决办法。
var animate1 = function() {
return $('.animateEle1').animate({opacity:'.5'},4000).promise();
};
var animate2 = function() {
return $('.animateEle2').animate({width:'100px'},2000).promise();
};
var animate3 = function(){
return $('.animateEle3').animate({height:'0'},2000).promise();
};
// so easy,有木有,so clear,有木有
$.when(animate1()).then(animate2).then(animate3);
很明显,更改后的代码更易懂易读了。
但是,上面的代码,有些细节的东西并没有透露,一不小心,就容易出错,得不到我们想要的顺序完成动画的效果。下面让我们来全面理解 jQuery
提供的 promise
和deferred
对象的方法,看看到底如何使用。
三、promise和deffered对象方法
promise
对象其实就是 deferred
对象的特例,因为 promise
对象不能更改异步状态,而 deferred
对象可以。这点在他们的方法设计上,有着明显的体现。
1.promise对象方法
通常,对于DOM,动画,ajax相关方法,我们都可以使用 promise
方法。调用 promise
方法,返回的是 promise
对象。可以链式调用 promise
方法。
promise对象常见的方法有三个 : done
, fail
, then
。
其它的方法就不要去记了, jquery
这里的接口方法太多了,在我看来挺啰嗦的,就跟早期的事件方法绑定一样, live
, delegate
, bind
,最终不是都归为on
来管了么。
代码示例,如下:
1.DOM使用 promise
方法:
var box=$('#box');
box.promise().done(function(ele){
console.log(ele);//jQuery box
});
2.Ajax使用 promise
方法(默认返回一个 promise
对象,所以可以不必显式调用 promise
方法):
$.post('/',{}).done(function(data){
console.log('请求成功');
}).fail(function(){
console.log('请求错误');
});
动画示例已有,就不重复列出了。
2.deferred对象方法
对于 deferred
对象呢,也就是使用 $.Deferred()
方法,以及 $.when()
等方法创造出来的对象,有如下的常用方法:
resolve
,reject
,notify
;done
,fail
,progress
;
另外还有 promise
、 then
和 always
方法。
之所以这么排版,是因为他们是对应的,也就是说: resolve
方法会触发 done
的回调执行, reject
会触发 fail
的回调, notify
会触发 progress
的回调。
直接看代码:
var wait = function(ms) {
var dtd = $.Deferred();
setTimeout(dtd.resolve, ms);
// setTimeout(dtd.reject, ms);
// setTimeout(dtd.notify, ms);
return dtd.promise(); //此处也可以直接返回dtd
};
wait(2500).done(function() {
console.log('haha,师太,你可让老衲久等了');
}).fail(function() {
console.log('失败了');
}).progress(function(res) {
console.log('等待中...');
});
我们看到了,上面的代码中,在 wait
函数中,返回的是个 promise
对象,而不是deferred
对象。
要知道, promise
对象是没有 resolve
, reject
, notify
等方法的,也就意味着,你无法针对 promise
对象进行状态更改,只能在 done
或 fail
中进行回调配置。所以,你如果这么调用 wait(2500).resolve()
将会报错,因为 wait(2500)
返回的是个 promise
对象,不存在 resolve
方法。
但是,这么做,有个好处,我们把 dtd
这个 deferred
对象放在了 wai
t函数中,作为了局部变量,避免了全局的污染;进一步通过 promise
方法,转化 dtd
这个 deferred
对象为 promise
对象,避免了函数 wait
外部可能发生的状态更改(假如我们确实有这个需求)。
比如:
var wait = function(ms) {
var dtd = $.Deferred();
setTimeout(dtd.resolve, ms);
// setTimeout(dtd.reject, ms);
// setTimeout(dtd.notify, ms);
return dtd; //此处也可以直接返回dtd
};
wait(2500).reject().fail(function(){
console.log('失败了...............');
});
我们在外部更改了 wait
返回的 deferred
对象的状态,这样必然触发该对象的 fail
回调函数。
对于 always
方法,从字面意思上就很容易理解, deferred
对象无论是 resolve
还是 reject
,都会触发该方法的回调。
3.其它共性
此处讲讲 then
和 $.when
方法的使用。它们对 promise
对象也适用。
$.when
方法接受多个deferred
对象或者纯javascript对象,返回promise
对象。then
方法依次接受三个回调,分别为deferred
对象resolve
,reject
,notify
后触发的回调,返回一个promise
对象。注意,必须传入函数,而该函数只有返回一个promise
对象,才能够让异步事件按照预期顺序来执行。
我们来看看最开始的动画示例代码, $.when(animate1()).then(animate2).then(animate3)
, $.when
方法中接受了一个 animate1
的函数执行结果,也就是得到了一个 promise
对象,而后的 then
中,则只是接受了一个变量名,这样得到的结果是一个匿名的函数体,而该函数中返回的是 promise
对象。正好符合了我们对 then
接受参数的要求。
假如我们把执行语句改成: $.when(animate1()).then(animate2()).then(animate3())
,这样造成的结果就是三个动画同步执行了。与 $.when(animate1(),animate2(),animate3())
无异。
既然 then
是如此要求,那么与 then
方法类似的 done
, fail
, progress
也是一样的。
目前的ES
标准中还未支持Promise
对象,那么我们就自己动手,丰衣足食吧。思路大致是这样的,用2个数组(doneList
和failList
)分别存储成功时的回调函数队列和失败时的回调队列
state
: 当前执行状态,有pending
、resolved
、rejected
3种取值done
: 向doneList
中添加一个成功回调函数fail
: 向failList
中添加一个失败回调函数then
: 分别向doneList
和failList
中添加回调函数always
: 添加一个无论成功还是失败都会调用的回调函数resolve
: 将状态更改为resolved
,并触发绑定的所有成功的回调函数reject
: 将状态更改为rejected
,并触发绑定的所有失败的回调函数when
: 参数是多个异步或者延迟函数,返回值是一个Promise兑现,当所有函数都执行成功的时候执行该对象的resolve
方法,反之执行该对象的reject
方法
下面是我的具体实现过程:- var Promise = function() {
- this.doneList = [];
- this.failList = [];
- this.state = 'pending';
- };
- Promise.prototype = {
- constructor: 'Promise',
- resolve: function() {
- this.state = 'resolved';
- var list = this.doneList;
- for(var i = 0, len = list.length; i < len; i++) {
- list[0].call(this);
- list.shift();
- }
- },
- reject: function() {
- this.state = 'rejected';
- var list = this.failList;
- for(var i = 0, len = list.length; i < len; i++){
- list[0].call(this);
- list.shift();
- }
- },
- done: function(func) {
- if(typeof func === 'function') {
- this.doneList.push(func);
- }
- return this;
- },
- fail: function(func) {
- if(typeof func === 'function') {
- this.failList.push(func);
- }
- return this;
- },
- then: function(doneFn, failFn) {
- this.done(doneFn).fail(failFn);
- return this;
- },
- always: function(fn) {
- this.done(fn).fail(fn);
- return this;
- }
- };
- function when() {
- var p = new Promise();
- var success = true;
- var len = arguments.length;
- for(var i = 0; i < len; i++) {
- if(!(arguments[i] instanceof Promise)) {
- return false;
- }
- else {
- arguments[i].always(function() {
- if(this.state != 'resolved'){
- success = false;
- }
- len--;
- if(len == 0) {
- success ? p.resolve() : p.reject();
- }
- });
- }
- }
- return p;
- }