標籤:使用 程式碼範例 div 方案 需要 需求 渲染 方便 問題
引子
相信各位developers對js中的非同步概念不會陌生,非同步作業後的邏輯由回呼函數來執行,回呼函數(callback function)顧名思義就是“回頭調用的函數”,函數體事先已定義好,在未來的某個時候由某個事件觸發調用,而這個時機,是程式本身無法控制的。
舉幾個常見例子:
上面的例子簡單、典型,易於閱讀和理解。
為了引出本文的主題,假設現在有3個ajax非同步作業,分別為A、B、C,每個都封裝成了函數,並可傳入success回調作為參數。
請考慮以下情境:
希望這3個非同步作業按順序執行,形成執行隊列,即A執行完了,B執行;B執行完了,C執行;
希望A,B完成了,再執行C;
對於情境(1),代碼大概會是這樣:
按傳統的寫法,自然而然會形成回呼函數的多層嵌套,如果代碼量比較多的話,代碼的可讀性和維護性會比較差。
對於情境(2),代碼可能要複雜些了:
就本情境而言,上述方案還可勉強接受,如果情況再複雜些,代碼的可讀性和維護性也會大打折扣。
以上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理解成你的一個手下,你讓他去跟進一件事,而這個事情什麼時候可以有個結果,你並不清楚,但他會給你承諾,將按照你的意圖:事情若這樣,後續要做什麼;事情若那樣,後續要做什麼
Jquery中的非同步編程淺析 延期(deferred)的承諾(promise)