首頁 >web前端 >js教程 >jQuery的介紹延期對象

jQuery的介紹延期對象

Christopher Nolan
Christopher Nolan原創
2025-02-18 11:08:10420瀏覽

An Introduction to jQuery's Deferred Objects

JavaScript開發者長期以來一直使用回調函數來執行多項任務。一個非常常見的例子是通過addEventListener()函數添加回調,以便在觸發點擊或按鍵等事件時執行各種操作。回調函數簡單易用,適用於簡單場景。然而,當網頁複雜度增加,需要並行或順序執行許多異步操作時,回調函數就會變得難以管理。

ECMAScript 2015 (又名ECMAScript 6) 引入了一種處理此類情況的原生方法:Promise。如果您不了解Promise,可以閱讀文章《JavaScript Promise概述》。 jQuery 提供並仍然提供其自己的Promise版本,稱為Deferred對象。在Promise被引入ECMAScript之前數年,Deferred對象就已經被引入jQuery。本文將討論Deferred對像是什麼,以及它們試圖解決什麼問題。

關鍵要點

  • Deferred對像簡化異步編程: jQuery的Deferred對象提供了一種強大的方法來管理和協調異步操作,降低了傳統回調函數相關的複雜性。
  • 與ECMAScript Promise的互操作性: 儘管實現方式有所不同,但3.x版本的jQuery Deferred對象改進了與原生ECMAScript 2015 Promise的兼容性,增強了其在現代Web開發中的實用性。
  • 方法靈活: Deferred對象提供了多種方法,例如resolve()reject()done()fail()then(),允許開發者精細控制異步流程的處理。
  • 避免回調地獄: 通過使用Deferred和Promise對象,開發者可以避免深度嵌套回調的常見陷阱,從而編寫更易讀和易維護的代碼。
  • 增強的錯誤處理: jQuery 3引入了Deferred對象的catch()方法,與ECMAScript標准保持一致,並提供了一種簡化的方法來處理Promise鏈中的錯誤。

簡史

Deferred對像在jQuery 1.5中引入,它是一個可鍊式調用的實用程序,用於將多個回調註冊到回調隊列中,調用回調隊列,並傳遞任何同步或異步函數的成功或失敗狀態。從那時起,它就一直是討論、一些批評和許多變化的主題。一些批評的例子包括《你錯過了Promise的重點》和《JavaScript Promise以及為什麼jQuery的實現是錯誤的》。

與Promise對像一起,Deferred代表了jQuery對Promise的實現。在jQuery 1.x和2.x版本中,Deferred對象遵循CommonJS Promises/A提案。該提案被用作Promises/A 提案的基礎,而原生Promise就是基於此提案構建的。如引言中所述,jQuery不遵循Promises/A 提案的原因是,它在該提案提出之前很久就實現了Promise。

因為jQuery是先驅,並且由於向後兼容性問題,在純JavaScript和jQuery 1.x和2.x中使用Promise的方式存在差異。此外,由於jQuery遵循不同的提案,該庫與其他實現了Promise的庫(如Q庫)不兼容。

在即將推出的jQuery 3中,與原生Promise(如ECMAScript 2015中實現的)的互操作性得到了改進。出於向後兼容性的原因,主要方法(then())的簽名仍然略有不同,但行為更符合標準。

jQuery中的回調函數

為了理解為什麼您可能需要使用Deferred對象,讓我們討論一個例子。使用jQuery時,經常使用其Ajax方法執行異步請求。為了舉例說明,假設您正在開發一個向GitHub API發送Ajax請求的網頁。您的目標是檢索用戶的存儲庫列表,找到最近更新的存儲庫,找到名稱中包含字符串“README.md”的第一個文件,最後檢索該文件的內容。根據此描述,每個Ajax請求只有在上一步完成後才能開始。換句話說,請求必須按順序運行。

將此描述轉換為偽代碼(請注意,我沒有使用真實的GitHub API),我們得到:

<code class="language-javascript">var username = 'testuser';
var fileToSearch = 'README.md';

$.getJSON('https://api.github.com/user/' + username + '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;

  $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/files', function(files) {
    var README = null;

    for (var i = 0; i < files.length; i++) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;

        break;
      }
    }

    $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/file/' + README + '/content', function(content) {
      console.log('The content of the file is: ' + content);
    });
  });
});</code>

正如您在這個例子中看到的,使用回調函數,我們必須嵌套調用來按我們想要的順序執行Ajax請求。這使得代碼的可讀性降低。有很多嵌套回調,或者必須同步的獨立回調的情況,通常被稱為“回調地獄”。

為了稍微改進一下,您可以從我創建的匿名內聯函數中提取命名函數。但是,此更改並沒有多大幫助,我們仍然發現自己處於回調地獄中。這時就需要Deferred和Promise對象了。

Deferred和Promise對象

在執行異步操作(例如Ajax請求和動畫)時,可以使用Deferred對象。在jQuery中,Promise對像是從Deferred對像或jQuery對象創建的。它擁有Deferred對象方法的一個子集:always()done()fail()state()then()。我將在下一節中介紹這些方法和其他方法。

如果您來自原生的JavaScript世界,您可能會對這兩個對象的存在感到困惑。為什麼當JavaScript只有一個(Promise)時會有兩個對象(Deferred和Promise)?為了解釋差異及其用例,我將採用我在我的書《jQuery in Action,第三版》中使用的相同比喻。

如果您編寫處理異步操作並應返回值(也可以是錯誤或根本沒有值)的函數,則通常使用Deferred對象。在這種情況下,您的函數是值的生產者,您希望阻止用戶更改Deferred的狀態。當您是函數的使用者時,使用Promise對象。

為了闡明這個概念,假設您想實現一個基於Promise的timeout()函數(我將在本文的後續部分向您展示此例子的代碼)。您負責編寫必須等待給定時間量的函數(在這種情況下不返回值)。這使您成為生產者。您的函數的使用者不關心解析或拒絕它。使用者只需要能夠添加函數以在Deferred的完成、失敗或進度時執行。此外,您希望確保使用者無法自行解析或拒絕Deferred。為了實現此目標,您需要返回在timeout()函數中創建的Deferred的Promise對象,而不是Deferred本身。通過這樣做,您可以確保除了timeout()函數之外,沒有人可以調用resolve()reject()方法。

您可以在此StackOverflow問題中閱讀更多關於jQuery的Deferred和Promise對象之間區別的信息。

現在您知道了這些對像是什麼,讓我們來看看可用的方法。

Deferred方法

Deferred對象非常靈活,並提供滿足您所有需求的方法。可以通過調用jQuery.Deferred()方法創建它,如下所示:

<code class="language-javascript">var username = 'testuser';
var fileToSearch = 'README.md';

$.getJSON('https://api.github.com/user/' + username + '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;

  $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/files', function(files) {
    var README = null;

    for (var i = 0; i < files.length; i++) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;

        break;
      }
    }

    $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/file/' + README + '/content', function(content) {
      console.log('The content of the file is: ' + content);
    });
  });
});</code>

或者,使用$快捷方式:

<code class="language-javascript">var deferred = jQuery.Deferred();</code>

創建後,Deferred對象會公開多個方法。忽略那些已棄用或刪除的方法,它們是:

  • always(callbacks[, callbacks, ..., callbacks]):添加要在Deferred對像被解析或拒絕時調用的處理程序。
  • done(callbacks[, callbacks, ..., callbacks]):添加要在Deferred對像被解析時調用的處理程序。
  • fail(callbacks[, callbacks, ..., callbacks]):添加要在Deferred對像被拒絕時調用的處理程序。
  • notify([argument, ..., argument]):使用給定的參數調用Deferred對象的progressCallbacks
  • notifyWith(context[, argument, ..., argument]):使用給定的上下文和參數調用Deferred對象的progressCallbacks
  • progress(callbacks[, callbacks, ..., callbacks]):添加要在Deferred對像生成進度通知時調用的處理程序。
  • promise([target]):返回Deferred的Promise對象。
  • reject([argument, ..., argument]):拒絕Deferred對象並使用給定的參數調用任何failCallbacks
  • rejectWith(context[, argument, ..., argument]):拒絕Deferred對象並使用給定的上下文和參數調用任何failCallbacks
  • resolve([argument, ..., argument]):解析Deferred對象並使用給定的參數調用任何doneCallbacks
  • resolveWith(context[, argument, ..., argument]):解析Deferred對象並使用給定的上下文和參數調用任何doneCallbacks
  • state():確定Deferred對象的當前狀態。
  • then(resolvedCallback[, rejectedCallback[, progressCallback]]):添加要在Deferred對像被解析、拒絕或仍在進行中時調用的處理程序。

這些方法的描述使我有機會強調jQuery文檔和ECMAScript規範使用的術語之間的區別。在ECMAScript規範中,當Promise已完成或拒絕時,據說Promise已解析。然而,在jQuery的文檔中,單詞“resolved”用於指代ECMAScript規範稱為“fulfilled”的狀態。

由於提供的方法數量眾多,本文無法涵蓋所有方法。但是,在接下來的部分中,我將向您展示Deferred和Promise使用的幾個示例。在第一個示例中,我們將重寫“jQuery中的回調函數”部分中檢查的代碼片段,但我們將使用這些對象而不是回調函數。在第二個示例中,我將闡明討論的生產者-消費者類比。

使用Deferred順序執行Ajax請求

在本節中,我將展示如何使用Deferred對象及其一些方法來提高“jQuery中的回調函數”部分中開發的代碼的可讀性。在深入研究之前,我們必須了解我們需要哪些方法。

根據我們的需求和提供的方法列表,很明顯我們可以使用done()then()方法來管理成功的情況。由於你們中的許多人可能已經習慣了JavaScript的Promise對象,因此在這個例子中,我將使用then()方法。這兩個方法之間的一個重要區別是then()能夠將接收到的參數值轉發到在其之後定義的其他then()done()fail()progress()調用。

最終結果如下所示:

<code class="language-javascript">var username = 'testuser';
var fileToSearch = 'README.md';

$.getJSON('https://api.github.com/user/' + username + '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;

  $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/files', function(files) {
    var README = null;

    for (var i = 0; i < files.length; i++) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;

        break;
      }
    }

    $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/file/' + README + '/content', function(content) {
      console.log('The content of the file is: ' + content);
    });
  });
});</code>

如您所見,代碼更易讀,因為我們能夠將整個過程分解成幾個處於同一級別(關於縮進)的小步驟。

創建基於Promise的setTimeout函數

如您所知,setTimeout()是一個在給定時間量後執行回調函數的函數。這兩個元素(回調函數和時間)都應作為參數提供。假設您想在一秒鐘後將消息記錄到控制台。通過使用setTimeout()函數,您可以使用下面顯示的代碼實現此目標:

<code class="language-javascript">var deferred = jQuery.Deferred();</code>

如您所見,第一個參數是要執行的函數,第二個參數是要等待的毫秒數。此函數多年來一直運行良好,但是如果您需要在Deferred鏈中引入延遲怎麼辦?

在下面的代碼中,我將向您展示如何使用jQuery提供的Promise對象來開發基於Promise的setTimeout()函數。為此,我將使用Deferred對象的promise()方法。

最終結果如下所示:

<code class="language-javascript">var deferred = $.Deferred();</code>

在此列表中,我定義了一個名為timeout()的函數,它包裝了JavaScript的原生setTimeout()函數。在timeout()內部,我創建了一個新的Deferred對象來管理一個異步任務,該任務包括在指定的毫秒數後解析Deferred對象。在這種情況下,timeout()函數是值的生產者,因此它創建Deferred對象並返回Promise對象。通過這樣做,我確保函數的調用者(使用者)無法隨意解析或拒絕Deferred對象。事實上,調用者只能使用done()fail()等方法添加要執行的函數。

jQuery 1.x/2.x和jQuery 3之間的區別

在使用Deferred的第一個示例中,我們開發了一個查找名稱中包含字符串“README.md”的文件的代碼片段,但是我們沒有考慮找不到此類文件的情況。這種情況可以看作是失敗。當這種情況發生時,我們可能希望中斷調用鏈並直接跳到其末尾。為此,自然會拋出異常並使用fail()方法捕獲它,就像使用JavaScript的catch()方法一樣。

在符合Promises/A和Promises/A 的庫中(例如,jQuery 3.x),拋出的異常將轉換為拒絕,並且將調用失敗回調(例如使用fail()添加的回調)。這將異常作為參數接收。

在jQuery 1.x和2.x中,未捕獲的異常將停止程序的執行。這些版本允許拋出的異常冒泡,通常到達window.onerror。如果未定義任何函數來處理此異常,則顯示異常消息併中止程序的執行。

為了更好地理解不同的行為,請查看此示例:

<code class="language-javascript">var username = 'testuser';
var fileToSearch = 'README.md';

$.getJSON('https://api.github.com/user/' + username + '/repositories', function(repositories) {
  var lastUpdatedRepository = repositories[0].name;

  $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/files', function(files) {
    var README = null;

    for (var i = 0; i < files.length; i++) {
      if (files[i].name.indexOf(fileToSearch) >= 0) {
        README = files[i].path;

        break;
      }
    }

    $.getJSON('https://api.github.com/user/' + username + '/repository/' + lastUpdatedRepository + '/file/' + README + '/content', function(content) {
      console.log('The content of the file is: ' + content);
    });
  });
});</code>

在jQuery 3.x中,此代碼將消息“First failure function”和“Second success function”寫入控制台。原因是,正如我之前提到的,規範指出拋出的異常應轉換為拒絕,並且必須使用異常調用失敗回調。此外,一旦異常被處理(在我們的示例中由傳遞給第二個then()的失敗回調處理),則應執行以下成功函數(在本例中是傳遞給第三個then()的成功回調)。

在jQuery 1.x和2.x中,除了第一個函數(拋出錯誤的函數)之外,沒有其他函數被執行,您只會看到控制台顯示的消息“Uncaught Error: An error message”。

為了進一步提高其與ECMAScript 2015的兼容性,jQuery 3還在Deferred和Promise對像中添加了一個新方法catch()。這是一種在Deferred對像被拒絕或其Promise對象處於拒絕狀態時執行處理程序的方法。其簽名如下:

<code class="language-javascript">var deferred = jQuery.Deferred();</code>

此方法只不過是then(null, rejectedCallback)的快捷方式。

結論

在本文中,我向您介紹了jQuery對Promise的實現。 Promise允許您避免使用討厭的技巧來同步並行異步函數以及嵌套回調的需要……

除了展示一些示例之外,我還介紹了jQuery 3如何改進與原生Promise的互操作性。儘管突出了舊版jQuery和ECMAScript 2015之間的差異,但Deferred仍然是您工具箱中一個非常強大的工具。作為一名專業開發人員,隨著項目難度的增加,您會發現自己經常使用它。

jQuery Deferred對象的常見問題解答 (FAQ)

(此處省略了FAQ部分,因為篇幅過長,且與文章主旨關係不大。如果需要,可以單獨提出FAQ問題。)

以上是jQuery的介紹延期對象的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn