Jquery中的非同步編程淺析 延期(deferred)的承諾(promise)

來源:互聯網
上載者:User

標籤:使用   程式碼範例   div   方案   需要   需求   渲染   方便   問題   

引子

相信各位developers對js中的非同步概念不會陌生,非同步作業後的邏輯由回呼函數來執行,回呼函數(callback function)顧名思義就是“回頭調用的函數”,函數體事先已定義好,在未來的某個時候由某個事件觸發調用,而這個時機,是程式本身無法控制的。

舉幾個常見例子:

  • 事件綁定

  • 動畫

  • Ajax

上面的例子簡單、典型,易於閱讀和理解。

為了引出本文的主題,假設現在有3個ajax非同步作業,分別為A、B、C,每個都封裝成了函數,並可傳入success回調作為參數。

請考慮以下情境:

  1. 希望這3個非同步作業按順序執行,形成執行隊列,即A執行完了,B執行;B執行完了,C執行;

  2. 希望A,B完成了,再執行C;

對於情境(1),代碼大概會是這樣:

按傳統的寫法,自然而然會形成回呼函數的多層嵌套,如果代碼量比較多的話,代碼的可讀性和維護性會比較差。

對於情境(2),代碼可能要複雜些了:

  • a) A、B操作分別需要有狀態變數isADoneisBDone,預設值均為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(拒絕)後失敗。

狀態無法復原,是指一旦從待定狀態切換到任何一個確定狀態後,再次調用resolvereject對原狀態將不起任何作用。

deferred通過語義對應的api來綁定不同狀態時執行的回呼函數。

resolve: done, always

reject: fail, always

其中always綁定的回呼函數,不論deferred的狀態是成功或失敗,總會執行。

deferred還有一個then方法,來簡化donefail的寫法:

另外,值得注意的是,當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寫法,我們也可以這麼寫:

其中donefail等方法還可以接受多個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回調;

解決方案已經很清晰了,我們稍加改造之前的asyncAasyncB方法,讓它們分別返回各自的promise,然後直接使用$.when即可。

我可以先用ajax來做程式碼範例:

下面再來個直接使用deferred的代碼執行個體:

整體上,是否感覺代碼更清晰,更利於理解了呢?

我們再來看情境(1):A執行完了,B執行;B執行完了,C執行。

要完成這個實現的改造,需要深入瞭解下deferred對象的then方法。上面我們介紹了then的一般用法:

用於donefail的簡化寫法。但它還有一個非常重要的作用,可以用來傳遞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)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.