$.ajax()引發的對Deferred的總結


傳統的ajax寫法:

$.ajax({
    url:"1.json",
    type:"get",
    success:function(data){},
    error:function(){}
});

jquery 1.7以后的新寫法,

$.ajax({
    url:"1.json",
    type:"get"    
}).done(function(data){
    
}).fail(function(){
    
});

我就納悶了.$.ajax()返回的是XMLHttpRequest對象.

我們都知道XMLHttpRequest是ajax的一個核心對象,用於和服務器交互的,
可是XMLHttpRequest對象根本就沒有什么done,fail方法,
這里的方法是怎么加上去的呢?
我們從done入手.在官網api上搜索done.

發現一個Deferred關鍵詞.

我們現在就從jQuery的Deferred開始講起吧.

Deferred對象是jquery開發團隊設計的,為了增強jquery的回調功能,在1.7版本中加入了jquery.
defer英語意思是延遲的意思.那么他是如何延遲的呢?
首先來理解一下基本知識,要不然后面沒法說了.
$.Deferred([fun])
$.Deferred()沒有參數時 返回Deferred對象;
有參數時,表示在這個參數上做延遲或異步操作,並且返回Deferred對象.
done(fn)        當延遲成功后調用該方法
fail(fn)        當延遲失敗時調用失敗
then(done,fail)        這個是done和fail的總寫方式
always(fn)        不管延遲執行的成功還是失敗,都執行的方法
上面的fn可能是一個function,也有可能是多個以逗號分隔的function函數

resolve和reject方法一旦執行,表示開始執行done,fail,then,always方法,
注意Deferred對象可以一次掛接多個done,fail方法,按照你分布的順序依次執行.

resolve(value)        告訴對象執行done回調,value是參數
reject(value)        告訴對象執行fail回調,value是參數.
調用done回調代碼:

var dfd = $.Deferred();
dfd.done(function(value) {
    alert(value);
});
dfd.resolve("執行done回調");

調用fail回調代碼:

var dfd = $.Deferred();
dfd.done(function(value) {
    alert(value);
});
dfd.reject("執行fail回調");

調用then回調代碼:

var dfd = $.Deferred();
var doneFun=function(value) {
    alert("done:"+value);
};
var failFun=function(value) {
    alert("fail:"+value);
}
dfd.then(doneFun,failFun);
dfd.reject("思思博士");

調用always回調代碼:

var dfd = $.Deferred();
var doneFun = function(value) {
    alert("done:" + value);
};
var failFun = function(value) {
    alert("fail:" + value);
}
dfd.then(doneFun, failFun).always(function() {
    alert("不管你延遲成功還是失敗,我都要執行always.");
});
dfd.resolve("我要執行done,但是這不影響我執行always!");

state()        返回deferred對象目前的狀態

1.pending:操作沒有完成
2.resolved:操作成功
3.rejected:操作失敗
代碼:

var dfd=$.Deferred();
dfd.done(function(){
    console.log("done");
});
console.log(dfd.state());
dfd.resolve();
console.log(dfd.state());

上面我們說了:resolve表示立馬執行回調,
所以這個地方的彈出是這樣的

黃金搭檔 progress(fn)和notify(value)

progress和done之類的方法截然不同.
progress(fn)的fn表示一個回調函數,這個回調函數由notify()方法通知執行.
代碼:

var dfd=$.Deferred();
dfd.progress(function(data){
    alert(data);
}).done(function(data){
    alert("done:>>>>>"+data);
}).fail(function(data){
    alert("fail:>>>>"+data);
});
function getProcess(){
    dfd.notify("我是progress回調函數的參數");
    var a=true;
    //下面判斷是為了執行done還是fail
    if(a){
        dfd.resolve("執行done.....");
    }else{
        dfd.reject("執行fail......");
    }
}
<input type="button" value="確定" onclick="getProcess()" />

根據以上分析,Deferred對象才有done這樣的反法,
根據$.ajax().done()推測$.ajax()返回的應該是Deferred對象?
姑且認為這個結論是對的吧.
那么jQuey除了$.ajax()有done方法外,還有哪些東西有done方法.
$.when(d);d是一個或多個延遲對象,或者普通的JavaScript對象。
下面看一下參數是一個普通對象的.

$.when({address:"china",sex:"男"})
.done(function(data){
    console.log(data.address)
});

這個例子主要是為了演示參數的傳遞的
前面說了同一延遲的多個回調的兩種寫法
看下面:

1.

$.ajax("1.json")
.done(function(data){
    alert("done1")
}).done(function(){
    alert("done2");
});

2.

$.ajax("1.json")
.done(function(data){
    alert("done1")
},function(){
    alert("done2");
});

結合when用法

$.when($.ajax("1.json"))
.done(function(data){
    console.log(data);
});

現在說一說多個延遲的同一個調用

$.when($.ajax("1.json"), $.ajax("2.json"),$.ajax("3.json"))
.done(function(data1, data2,data3) {
    console.log(data1);
    console.log(data2);
    console.log(data3);
}).fail(function(data1,data2,data3){
    console.log(data1);
    console.log(data2);
    console.log(data3);
});

這個地方返回值有點臃腫,他把$.ajax()對象給返回回來了
這個地方的用的是ajax做的延遲,
那么我們能不能用setTimeout模擬延遲

function delay(){
    setTimeout(function(){
        alert("執行啦");
    },2000);
}        
$.when(delay())
.done(function(){
    alert("done");
});

不對,這個地方首先執行的是done,2s后執行的才是setTimeout.
思考思考,想一想上面的參數.
這個地方最多只是將delay()當做參數傳遞給done回調函數的參數.因為不是一個Deferred類型的參數.
因為$.ajax()返回Deferred類型才能使用done一樣.
那么要怎么樣才能讓delay返回一個Deferred對象來完成我們的延遲模擬呢.
現在讓我們回想一下上面的第一串代碼$.Deferred();
這個可以返回Deferred類型.
改寫上面代碼:

var dfd=$.Deferred();        
function delay(dfd){
    var bool=true;
    setTimeout(function(){
        alert("delay的setTimeout執行啦!");
        if(bool){
            dfd.resolve("done");    
        }else{
            dfd.reject("fail");
        }
        
    },2000);
    return dfd;
}

$.when(delay(dfd))
.done(function(value){
    alert(value);
}).fail(function(value){
    alert(value);
});

這個時候我們終於實現了延遲,回到功能.
上面說了 done只要一遇到resolve或fail遇到reject就會立即執行,
那么我們在底部添加一行代碼:
修改上面的代碼:

var dfd=$.Deferred();        
function delay(dfd){
    var bool=true;
    setTimeout(function(){
        console.log("delay的setTimeout執行啦!");
        if(bool){
            dfd.resolve("done");    
        }else{
            dfd.reject("fail");
        }
        
    },2000);
    return dfd;
}

$.when(delay(dfd))
.done(function(value){
    console.log("done1"+value);            
}).fail(function(value){
    console.log("fail1"+value);
});
dfd.resolve("我是來搗亂的....");

順序變成了這樣:

很明顯破壞了我們模擬延遲的目的.
如何解決
將dfd從全局改成局部變量,這樣別人就不能輕易的改變狀態了.

function delay(){
    var dfd=$.Deferred();    
    var bool=true;
    setTimeout(function(){
        alert("delay的setTimeout執行啦!");
        if(bool){
            dfd.resolve("done");    
        }else{
            dfd.reject("fail");
        }
    },2000);
    return dfd;
}
$.when(delay())
.done(function(value){
    alert("done1"+value);            
}).fail(function(value){
    alert("fail1"+value);
});

 還是那句話:done只要一遇到resolve或fail遇到reject就會立即執行
 改寫上面的代碼:

function delay(){
    var dfd=$.Deferred();    
    var bool=true;
    setTimeout(function(){
        console.log("delay的setTimeout執行啦!");
        if(bool){
            dfd.resolve("done");    
        }else{
            dfd.reject("fail");
        }
    },2000);
    return dfd;
}
var delay2=delay();
delay2.resolve();
$.when(delay())
.done(function(value){
    console.log("done1"+value);            
}).fail(function(value){
    console.log("fail1"+value);
});

這個時候執行變成這樣,2s后:

如果我們的Deferred可以這樣任意的被篡改,那么我們的程序健壯何在,
有沒有一個辦法讓外人無法訪問到Deferred對象,但是又不影響我們的回調函數的調用.
答案是有:promise();
改寫代碼:

function delay(){
    var dfd=$.Deferred();    
    var bool=true;
    setTimeout(function(){
        console.log("delay的setTimeout執行啦!");
        if(bool){
            dfd.resolve("done");    
        }else{
            dfd.reject("fail");
        }
    },2000);
    return dfd.promise();
}
//var delay2=delay();
//delay2.resolve();
$.when(delay())
.done(function(value){
    console.log("done1"+value);            
}).fail(function(value){
    console.log("fail1"+value);
});

這個時候你在把注釋放開,就會報錯了.

在看下面這串代碼的報錯:
$.ajax().resolve();

是不是很熟悉的報錯.
所以我們的$.ajax()更確切的說最終返回的是Promise對象.
$.ajax(),$.when()說白了不過是一個方法而已.
既然jQuery能夠搞個$.ajax(),$.when()接口出來,我們理所當然的也能搞一個
這樣的接口出來了.
回到最上面的那串代碼:
$.Deferred(fn);
我們一直都是在用無參的$.Deferred();
那這個fn參數是干嘛的呢?fn主要用來部署異步或延遲操作的.

 $.delay=$.Deferred(function(dfd){
    setTimeout(function(){
        alert("setTimeout執行啦!!!!");
        dfd.resolve();
    },2000);
});
$.delay.done(function(){
    alert("done1");
});

注意:這里的dfd不需要我們調用時傳遞,自己會傳一個Deferred對象進去
這樣看上去是不是就和$.ajax()和$.when()差不多了.
但是又不太一樣,$.ajax()和$.when()可以傳參數我們的$.delay不能傳遞參數,他也不是一個方法,就是一個Promise對象.
繼續優化上面的代碼,使之可以傳遞參數:

$.delay = function(time) {
    return $.Deferred(function(dfd) {
        setTimeout(function() {
            alert("setTimeout執行啦!!!!");
            dfd.resolve();
        }, time);
    })
}
$.delay(5000).done(function() {
    alert("done1");
});

代碼優化好了之后,功能實現了;
此時問題又來了.$.delay()是jQuery已經定義過的一個方法,我們定義的方法額jQuery重名了
不如我們吧$.delay()改成一個普通的方法,不更好.

var delay = function(time) {
    return $.Deferred(function(dfd) {
        setTimeout(function() {
            alert("setTimeout執行啦!!!!");
            dfd.resolve();
        }, time);
    })
}
delay(5000).done(function() {
    alert("done1");
});

或者是這樣:

function delay(time) {
    return $.Deferred(function(dfd) {
        setTimeout(function() {
            alert("setTimeout執行啦!!!!");
            dfd.resolve();
        }, time);
    })
}
delay(5000).done(function() {
    alert("done1");
});

上面說的是通過返回Promise對象使其有了done等接口,
能否直接在給出的函數上布置接口呢?這里就用到了我們之前用到的promise()
代碼如下:

var dfd=$.Deferred();
function delay(d,time) {
    setTimeout(function(){
        alert("setTimeout執行啦!!");
        d.resolve();
    },time);
}
dfd.promise(delay);
delay.done(function() {
    alert("done1");
});
delay(dfd,5000);

這里又出現了dfd這樣的全局變量,而且在調用時還是需要傳遞dfd參數,似乎有點不好看,優化一下:

function ansy(time) {
    var dfd = $.Deferred();
    function delay(d, time) {
        setTimeout(function() {
            alert("setTimeout執行啦!!");
            d.resolve("我被done彈出來了");
        }, time);
    }
    dfd.promise(delay);
    delay(dfd,time);
    return delay;
}

ansy(2000).done(function(value){
    alert("done說:"+value);
});

好了總算說完了.
現在我們看看jQuery1.7.2的ajax源碼:

為了閱讀方便,我把源碼刪掉了一部分,行號和真實的jquery行號對不上.
ajax最終返回的是jqXHR,而在7424行在bei部署成了Promise對象.
因此一開始的疑惑終於解決了,$.ajax()返回的是Promise,並非我們在官網上看到的是XMLHttpRequest對象.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM