jQuery.Deferred對象

jQuery.Deferred對象

來自《JavaScript 標準參考教程(alpha)》,by 阮一峯javascript

目錄

概述

deferred對象表明了將要完成的某種操做,並提供了一些方法,幫助用戶使用。它是jQuery對Promises接口的實現。jQuery的全部Ajax操做函數,默認返回的就是一個deferred對象。php

簡單說,Promises是異步操做的通用接口,扮演代理人(proxy)的角色,將異步操做包裝成具備同步操做特性的特殊對象。異步操做的典型例子就是Ajax操做、網頁動畫、web worker等等。html

因爲JavaScript單線程的特色,若是某個操做耗時很長,其餘操做就必需排隊等待。爲了不整個程序失去響應,一般的解決方法是將那些排在後面的操做,寫成「回調函數」(callback)的形式。這樣作雖然能夠解決問題,可是有一些顯著缺點:java

  • 回調函數每每寫成函數參數的形式,造成所謂的「持續傳遞風格」(即參數就是下一步操做,Continuation-passing style),致使函數的輸入和輸出很是混亂,整個程序的可閱讀性差;
  • 回調函數每每只能指定一個,若是有多個操做,就須要改寫回調函數。
  • 除了正常的報錯機制,錯誤還可能經過回調函數的形式返回,增長了除錯和調試的難度。
  • 正常的函數輸入和輸出能夠區分得很清楚,回調函數使得函數的輸出再也不重要。

Promises就是爲了解決這些問題而提出的,它的主要目的就是取代回調函數,成爲非同步操做的解決方案。它的核心思想就是讓非同步操做返回一個對象,其餘操做都針對這個對象來完成。好比,假定ajax操做返回一個Promise對象。jquery

var promise = get('http://www.example.com');

而後,Promise對象有一個then方法,能夠用來指定回調函數。一旦非同步操做完成,就調用指定的回調函數。git

promise.then(function (content) { console.log(content) })

能夠將上面兩段代碼合併起來,這樣程序的流程看得更清楚。github

get('http://www.example.com').then(function (content) { console.log(content) })

在1.7版以前,jQuery的Ajax操做採用回調函數。web

$.ajax({ url:"/echo/json/", success: function(response) { console.info(response.name); } });

1.7版以後,Ajax操做直接返回Promise對象,這意味着能夠用then方法指定回調函數。ajax

$.ajax({ url: "/echo/json/", }).then(function (response) { console.info(response.name); });

deferred對象的方法

基本用法

(1)生成deferred對象json

第一步是經過$.Deferred()方法,生成一個deferred對象。

var deferred = $.Deferred();

(2)deferred對象的狀態

deferred對象有三種狀態。

  • pending:表示操做尚未完成。
  • resolved:表示操做成功。
  • rejected:表示操做失敗。

state方法用來返回deferred對象當前狀態。

$.Deferred().state() // 'pending' $.Deferred().resolve().state() // 'resolved' $.Deferred().reject().state() // 'rejected'

(3)改變狀態的方法

resolve方法將deferred對象的狀態從pending改成resolved,reject方法則將狀態從pending改成rejected。

var deferred = $.Deferred(); deferred.resolve("hello world");

resolve方法的參數,用來傳遞給回調函數。

(4)綁定回調函數

deferred對象在狀態改變時,會觸發回調函數。

done方法指定狀態變爲resolved(操做成功)時的回調函數;fail方法指定狀態變爲rejected(操做失敗)時的回調函數;always方法指定,無論狀態變爲resolved或rejected,都會觸發的方法。

var deferred = $.Deferred(); deferred.done(function(value) { console.log(value); }).resolve('hello world'); // hello world

上述三種方法都返回的原有的deferred對象,所以能夠採用鏈式寫法,在後面再連接別的方法(包括done和fail在內)。

$.Deferred().done(f1).fail(f2).always(f3);

notify() 和 progress()

progress()用來指定一個回調函數,當調用notify()方法時,該回調函數將執行。它的用意是提供一個接口,使得在非同步操做執行過程當中,能夠執行某些操做,好比按期返回進度條的進度。

var userProgress = $.Deferred(); var $profileFields = $("input"); var totalFields = $profileFields.length userProgress.progress(function (filledFields) { var pctComplete = (filledFields/totalFields)*100; $("#progress").html(pctComplete.toFixed(0)); }); userProgress.done(function () { $("#thanks").html("Thanks for completing your profile!").show(); }); $("input").on("change", function () { var filledFields = $profileFields.filter("[value!='']").length; userProgress.notify(filledFields); if (filledFields == totalFields) { userProgress.resolve(); } });

then方法

(1)概述

then方法的做用也是指定回調函數,它能夠接受三個參數,也就是三個回調函數。第一個參數是resolve時調用的回調函數(至關於done方法),第二個參數是reject時調用的回調函數(至關於fail方法),第三個參數是progress()方法調用的回調函數。

deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

(2)返回值

在jQuery 1.8以前,then()只是.done().fail()寫法的語法糖,兩種寫法是等價的。在jQuery 1.8以後,then()返回一個新的promise對象,而done()返回的是原有的deferred對象。若是then()指定的回調函數有返回值,該返回值會做爲參數,傳入後面的回調函數。

var defer = jQuery.Deferred(); defer.done(function(a,b){ return a * b; }).done(function( result ) { console.log("result = " + result); }).then(function( a, b ) { return a * b; }).done(function( result ) { console.log("result = " + result); }).then(function( a, b ) { return a * b; }).done(function( result ) { console.log("result = " + result); }); defer.resolve( 2, 3 );

在jQuery 1.8版本以前,上面代碼的結果是:

result = 2 result = 2 result = 2 

在jQuery 1.8版本以後,返回結果是

result = 2 result = 6 result = NaN 

這一點須要特別引發注意。

$.ajax( url1, { dataType: "json" } ) .then(function( data ) { return $.ajax( url2, { data: { user: data.userId } } ); }).done(function( data ) { // 從url2獲取的數據 });

上面代碼最後那個done方法,處理的是從url2獲取的數據,而不是從url1獲取的數據。

(3)對返回值的修改

利用then()會修改返回值這個特性,咱們能夠在調用其餘回調函數以前,對前一步操做返回的值進行處理。

var post = $.post("/echo/json/") .then(function(p){ return p.firstName; }); post.done(function(r){ console.log(r); });

上面代碼先使用then()方法,從返回的數據中取出所須要的字段(firstName),因此後面的操做就能夠只處理這個字段了。

有時,Ajax操做返回json字符串裏面有一個error屬性,表示發生錯誤。這個時候,傳統的方法只能是經過done()來判斷是否發生錯誤。經過then()方法,可讓deferred對象調用fail()方法。

var myDeferred = $.post('/echo/json/', {json:JSON.stringify({'error':true})}) .then(function (response) { if (response.error) { return $.Deferred().reject(response); } return response; },function () { return $.Deferred().reject({error:true}); } ); myDeferred.done(function (response) { $("#status").html("Success!"); }).fail(function (response) { $("#status").html("An error occurred"); });

上面代碼中,不論是通訊出錯,或者服務器返回一個錯誤,都會調用reject方法,返回一個新的deferred對象,狀態爲rejected,所以就會觸發fail方法指定的回調函數。

關於error的處理,jQuery的deferred對象與其餘實現Promises規範的函數庫有一個重大不一樣。就是說,若是deferred對象執行過程當中,拋出一個非Promises對象的錯誤,那麼將不會被後繼的then方法指定的rejected回調函數捕獲,而會一直傳播到應用程序層面。爲了代碼行爲與Promises規範保持一致,建議出錯時,老是使用reject方法返回錯誤。

d = $.Deferred() d.then(function(){ throw new Error('err') }).fail(function(){ console.log('fail') }) d.resolve() // Error: err 

上面代碼中,then的回調函數拋出一個錯誤,按照Promises規範,應該被fail方法的回調函數捕獲,可是jQuery的部署是上升到應用程序的層面。

(4)回調函數的返回值

若是回調函數返回deferred對象,則then方法的返回值將是對應這個返回值的promise對象。

var d1 = $.Deferred(); var promise = $.when('Hello').then(function(h){ return $.when(h,d1); }) promise.done(function (s1,s2) { console.log(s1); console.log(s2); }) d1.resolve('World') // Hello // World

上面代碼中,done方法的回調函數,正常狀況下只能接受一個參數。可是因爲then方法的回調函數,返回一個when方法生成的deferred對象,致使它能夠接受兩個參數。

pipe方法

pipe方法接受一個函數做爲參數,表示在調用then方法、done方法、fail方法、always方法指定的回調函數以前,先運行pipe方法指定的回調函數。它一般用來對服務器返回的數據作初步處理。

與Promise A+規格的差別

Promise事實上的標準是社區提出的Promise A+規格,jQuery的實現並不徹底符合Promise A+,主要是對錯誤的處理。

var promise2 = promise1.then(function () { throw new Error("boom!"); }); 

上面代碼在回調函數中拋出一個錯誤,Promise A+規定此時Promise實例的狀態變爲reject,該錯誤被下一個catch方法指定的回調函數捕獲。可是,jQuery的Deferred對象此時不會改變狀態,亦不會觸發回調函數,該錯誤通常狀況下會被window.onerror捕獲。換句話說,在Deferred對象中,老是必須使用reject方法來改變狀態。

promise對象

(1)概念

通常狀況下,從外部改變第三方完成的異步操做(好比Ajax)的狀態是毫無心義的。爲了防止用戶這樣作,能夠在deferred對象的基礎上,返回一個針對它的promise對象。

簡單說,promise對象就是不能改變狀態的deferred對象,也就是deferred的只讀版。或者更通俗地理解成,promise是一個對將要完成的任務的承諾,排除了其餘人破壞這個承諾的可能性,只能等待承諾方給出結果。

你能夠經過promise對象,爲原始的deferred對象添加回調函數,查詢它的狀態,可是沒法改變它的狀態,也就是說promise對象不容許你調用resolve和reject方法。

(2)生成promise對象

deferred對象的promise方法,用來生成對應的promise對象。

function getPromise(){ return $.Deferred().promise(); } try{ getPromise().resolve("a"); } catch(err) { console.log(err); } // TypeError

上面代碼對promise對象,調用resolve方法,結果報錯。

jQuery的ajax()方法返回的就是一個Promise對象。此外,Animation類操做也可使用promise方法。

$('body').toggle('blinds').promise().then( function(){ $('body').toggle('blinds') } ) 

輔助方法

deferred對象還有一系列輔助方法,使它更方便使用。

$.when()方法

$.when()接受多個deferred對象做爲參數,當它們所有運行成功後,才調用resolved狀態的回調函數,但只要其中有一個失敗,就調用rejected狀態的回調函數。它至關於將多個非同步操做,合併成一個。實質上,when方法爲多個deferred對象,返回一個單一的promise對象。

$.when( $.ajax( "/main.php" ), $.ajax( "/modules.php" ), $.ajax( "/lists.php" ) ).then(successFunc, failureFunc);

上面代碼表示,要等到三個ajax操做都結束之後,才執行then方法指定的回調函數。

when方法裏面要執行多少個操做,回調函數就有多少個參數,對應前面每個操做的返回結果。

$.when( $.ajax( "/main.php" ), $.ajax( "/modules.php" ), $.ajax( "/lists.php" ) ).then(function (resp1, resp2, resp3){ console.log(resp1); console.log(resp2); console.log(resp3); });

上面代碼的回調函數有三個參數,resp一、resp2和resp3,依次對應前面三個ajax操做的返回結果。

若是when方法的參數不是deferred或promise對象,則直接做爲回調函數的參數。

d = $.Deferred() $.when(d, 'World').done(function (s1, s2){ console.log(s1); console.log(s2); }) d.resolve('Hello') // Hello // World

上面代碼中,when的第二個參數是一個字符串,則直接做爲回調函數的第二個參數。

此外,若是when方法的參數都不是deferred或promise對象,那麼when方法的回調函數將當即運行。

使用實例

wait方法

咱們能夠用deferred對象寫一個wait方法,表示等待多少毫秒後再執行。

$.wait = function(time) { return $.Deferred(function(dfd) { setTimeout(dfd.resolve, time); }); }

使用方法以下。

$.wait(5000).then(function() { console.log("Hello from the future!"); });

改寫setTimeout

在上面的wait方法的基礎上,還能夠改寫setTimeout方法,讓其返回一個deferred對象。

function doSomethingLater(fn, time) { var dfd = $.Deferred(); setTimeout(function() { dfd.resolve(fn()); }, time || 0); return dfd.promise(); } var promise = doSomethingLater(function (){ console.log( '已經延遲執行' ); }, 100);

自定義操做使用deferred接口

咱們能夠利用deferred接口,使得任意操做均可以用done()和fail()指定回調函數。

Twitter = { search:function(query) { var dfd = $.Deferred(); $.ajax({ url:"http://search.twitter.com/search.json", data:{q:query}, dataType:'jsonp', success:dfd.resolve }); return dfd.promise(); } }

使用方法以下。

Twitter.search('javaScript').then(function(data) { alert(data.results[0].text); });

deferred對象的另外一個優點是能夠附加多個回調函數。下面的例子使用了上面所改寫的setTimeout函數。

function doSomething(arg) { var dfd = $.Deferred(); setTimeout(function() { dfd.reject("Sorry, something went wrong."); }); return dfd; } doSomething("uh oh").done(function() { console.log("Won't happen, we're erroring here!"); }).fail(function(message) { console.log(message); });

參考連接

 | last modified on 2013-12-19

相關文章
相關標籤/搜索