大白話講解Promise(三)搞懂jquery中的Promise
前兩篇我們講了ES6中的Promise
以及Promise/A+
規範,在Promise
的知識體系中,jquery當然是必不可少的一環,所以本篇就來講講jquery中的Promise
,也就是我們所知道的Deferred
物件。
事實上,在此之前網上有很多文章在講jquery Deferred
物件了,但是總喜歡把ajax
和Deferred
混在一起講,容易把人搞混。when、done、promise、success、error、fail、then、resolve、reject、always
這麼多方法不能揉在一起講,需要把他們捋一捋,哪些是Deferred
物件的方法,哪些是ajax
的語法糖,我們需要心知肚明。
先講$.Deferred
jquery用$.Deferred
實現了Promise
規範,$.Deferred
是個什麼玩意呢?還是老方法,打印出來看看,先有個直觀印象:
var def = $.Deferred();
console.log(def);
輸出如下:
$.Deferred()
返回一個物件,我們可以稱之為Deferred
物件,上面掛著一些熟悉的方法如:done、fail、then
等。jquery
就是用這個Deferred
物件來註冊非同步操作的回撥函式,修改並傳遞非同步操作的狀態。
Deferred
物件的基本用法如下,為了不與ajax混淆,我們依舊舉setTimeout
function runAsync(){
var def = $.Deferred();
//做一些非同步操作
setTimeout(function(){
console.log('執行完成');
def.resolve('隨便什麼資料');
}, 2000);
return def;
}
runAsync().then(function(data){
console.log(data)
});
在runAsync
函式中,我們首先定義了一個def
物件,然後進行一個延時操作,在2秒後呼叫def.resolve()
def
作為函式的返回。呼叫runAsync
的時候將返回def
物件,然後我們就可以.then
來執行回撥函式。
是不是感覺和ES6
的Promise
很像呢?我們來回憶一下第一篇中ES6
的例子:
function runAsync(){
var p = new Promise(function(resolve, reject){
//做一些非同步操作
setTimeout(function(){
console.log('執行完成');
resolve('隨便什麼資料');
}, 2000);
});
return p;
}
runAsync()
區別在何處一看便知。由於jquery的def
物件本身就有resolve
方法,所以我們在建立def
物件的時候並未像ES6這樣傳入了一個函式引數,是空的。在後面可以直接def.resolve()
這樣呼叫。
這樣也有一個弊端,因為執行runAsync()
可以拿到def
物件,而def物件上又有resolve
方法,那麼豈不是可以在外部就修改def
的狀態了?比如我把上面的程式碼修改如下:
var d = runAsync();
d.then(function(data){
console.log(data)
});
d.resolve('在外部結束');
現象會如何呢?並不會在2秒後輸出“執行完成”,而是直接輸出“在外部結束”。因為我們在非同步操作執行完成之前,沒等他自己resolve
,就在外部給resolve
了。這顯然是有風險的,比如你定義的一個非同步操作並指定好回撥函式,有可能被別人給提前結束掉,你的回撥函式也就不能執行了。
怎麼辦?jquery提供了一個promise
方法,就在def
物件上,他可以返回一個受限的Deferred
物件,所謂受限就是沒有resolve、reject
等方法,無法從外部來改變他的狀態,用法如下:
function runAsync(){
var def = $.Deferred();
//做一些非同步操作
setTimeout(function(){
console.log('執行完成');
def.resolve('隨便什麼資料');
}, 2000);
return def.promise(); //就在這裡呼叫
}
這樣返回的物件上就沒有resolve
方法了,也就無法從外部改變他的狀態了。這個promise
名字起的有點奇葩,容易讓我們搞混,其實他就是一個返回受限Deferred
物件的方法,與Promise
規範沒有任何關係,僅僅是名字叫做promise
罷了。雖然名字奇葩,但是推薦使用。
then
的鏈式呼叫
既然Deferred
也是Promise
規範的實現者,那麼其他特性也必須是支援的。鏈式呼叫的用法如下:
var d = runAsync();
d.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return runAsync3();
})
.then(function(data){
console.log(data);
});
與我們第一篇中的例子基本一樣,可以參照。
done
與fail
我們知道,Promise
規範中,then
方法接受兩個引數,分別是執行完成和執行失敗的回撥,而jquery中進行了增強,還可以接受第三個引數,就是在pending
狀態時的回撥,如下:
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
除此之外,jquery還增加了兩個語法糖方法,done
和fail
,分別用來指定執行完成和執行失敗的回撥,也就是說這段程式碼:
d.then(function(){
console.log('執行完成');
}, function(){
console.log('執行失敗');
});
與這段程式碼是等價的:
d.done(function(){
console.log('執行完成');
})
.fail(function(){
console.log('執行失敗');
});
always
的用法
jquery的Deferred
物件上還有一個always
方法,不論執行完成還是執行失敗,always
都會執行,有點類似ajax
中的complete
。不贅述了。
$.when
的用法
jquery中,還有一個$.when
方法來實現Promise
,與ES6中的all
方法功能一樣,並行執行非同步操作,在所有的非同步操作執行完後才執行回撥函式。不過$.when
並沒有定義在$.Deferred
中,看名字就知道,$.when
,它是一個單獨的方法。與ES6的all
的引數稍有區別,它接受的並不是陣列,而是多個Deferred
物件,如下:
$.when(runAsync(), runAsync2(), runAsync3())
.then(function(data1, data2, data3){
console.log('全部執行完成');
console.log(data1, data2, data3);
});
jquery中沒有像ES6中的race
方法嗎?就是以跑的快的為準的那個方法。對的,jquery中沒有。
以上就是jquery中Deferred
物件的常用方法了,還有一些其他的方法用的也不多,乾脆就不記它了。接下來該說說ajax
了。
ajax與Deferred的關係
jquery的ajax
返回一個受限的Deferred
物件,還記得受限的Deferred
物件吧,也就是沒有resolve
方法和reject
方法,不能從外部改變狀態。想想也是,你發一個ajax
請求,別人從其他地方給你取消掉了,也是受不了的。
既然是Deferred
物件,那麼我們上面講到的所有特性,ajax
也都是可以用的。比如鏈式呼叫,連續傳送多個請求:
req1 = function(){
return $.ajax(/*...*/);
}
req2 = function(){
return $.ajax(/*...*/);
}
req3 = function(){
return $.ajax(/*...*/);
}
req1().then(req2).then(req3).done(function(){
console.log('請求傳送完畢');
});
明白了ajax
返回物件的實質,那我們用起來就得心應手了。
success、error與complete 這三個方法或許是我們用的最多的,使用起來是這樣的:
$.ajax(/*...*/)
.success(function(){/*...*/})
.error(function(){/*...*/})
.complete(function(){/*...*/})
分別表示ajax
請求成功、失敗、結束的回撥。這三個方法與Deferred
又是什麼關係呢?其實就是語法糖,success
對應done
,error
對應fail
,complete
對應always
,就這樣,只是為了與ajax
的引數名字上保持一致而已,更方便大家記憶,看一眼原始碼:
deferred.promise( jqXHR ).complete = completeDeferred.add;
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;
complete
那一行那麼寫,是為了減少重複程式碼,其實就是把done
和fail
又呼叫一次,與always
中的程式碼一樣。deferred.promise( jqXHR )
這句也能看出,ajax
返回的是受限的Deferred
物件。
jquery加了這麼些個語法糖,雖然上手門檻更低了,但是卻造成了一定程度的混淆。一些人雖然這麼寫了很久,卻一直不知道其中的原理,在面試的時候只能答出一些皮毛,這是很不好的。這也是我寫這篇文章的緣由。
jquery中Deferred
物件涉及到的方法很多,本文儘量分門別類的來介紹,希望能幫大家理清思路。總結一下就是:$.Deferred
實現了Promise
規範,then、done、fail、always
是Deferred
物件的方法。$.when
是一個全域性的方法,用來並行執行多個非同步任務,與ES6的all
是一個功能。ajax返回一個Deferred
物件,success、error、complete
是ajax
提供的語法糖,功能與Deferred
物件的done、fail、always
一致。就醬。