jQuery - AJAX 與 非同步
AJAX
同步(Synchronous)
在瀏覽器處理 script 標籤時,傳統上會先停止頁面其他內容的處理,直到程式碼均載入且處理完成為止。這樣的模式稱為「同步處理模型」(synchronous processing model)。換句話說,也就是等我做完,你才能進行。因此,當頁面正進行載入時,若程式需要自伺服器要求資料,瀏覽器不只需要等待程式的載入和執行,還需要等待伺服器回傳程式需呈現資料的時間。
非同步(Asynchronous)
瀏覽器能夠自伺服器要求資料,一旦送出資料請求,也同時可以載入頁面裡的其他內容,並同時處理使用者於頁面的互動操作。這樣同時進行的模式稱為非同步處理模型(Asynchronous processing model)。
AJAX 的 A 指的就是非同步(Asynchronous)。
以往的瀏覽器與 AJAX
以往資料傳遞步驟
觸發頁面上事件 > 送出 request 至伺服器(同步,伺服器處理時客戶端進入等待狀態) > 處理結果(一般狀況)以 HTM 格式回傳 > 整體頁面刷新(一次回傳頁面所有資料,負荷大)
AJAX 資料傳遞步驟
觸發頁面上事件 > 使用 JavaScript + XMLHttpRequest 對伺服器送出 request(非同步,伺服器處理 request 時客戶端可繼續操作) > 處理結果以 JSON 或 XML 等格式回傳 > 接收 response 並以 DOM 更新部分網頁(只回傳須更新的部分,負荷小)
所以甚麼是 AJAX?
AJAX,Asynchronous Javascript and XML (非同步的 Javasccript 與 XML 技術),在這些技術結合下,它是一個可載入資料至部份頁面區段中,而不需要重新整理整個頁面的技術。使用 AJAX 技術,換句話說,可以節省等待資料的時間,各做各的。
在傳統上,頁面載入完成,若想要更新瀏覽器視窗內的資訊讓使用者瀏覽,會需要重新整理整個頁面,這代表使用者必須等待新的頁面內容下載,並等待瀏覽器繪製完成。若使用 AJAX 技術,如果只需要更新一部分的頁面內容(因為使用者操作的只是部分),就可以只變更包含該部分內容的元素即可。也就是資料正進行載入時,使用者仍然可以繼續與頁面其他內容進行互動。接著,當伺服器回應後,一個特殊的 AJAX 事件將會觸發接收伺服器回應資料的程式,並僅更新頁面的部份資訊。因為不需要重新載入整個頁面,資料載入便會更快速,且使用這在等待資料下載的同時仍可與頁面互動。
藉由 AJAX 技術,頁面與 API 只會向伺服器要求它們真正需要的東西,也就是頁面上需要改變的部分,以及伺服器必須提供資料的部分。這表示較少的傳輸量,較少的更新以及較少的頁面重整的等待時間。傳輸資料目前以 JSON 結構格式為主流,在伺服器與瀏覽器之間進行資料的傳遞,而且不會干擾到使用者的操作,使用者可以在瀏覽器等待資料載入時,仍可以繼續進行其他操作,讓操作更為順暢。
AJAX 名詞解釋
A
AJAX 的 A 是 Asynchronous(非同步),指的是 Javascript 對伺服器提供請求,使用者仍然可透過輸入網頁表單、點擊按鈕與頁面互動,這互動所有一切都發在 Web 伺服器仍在執行。當伺服器完成工作時,程式碼可以只更新頁面上有發生改變的部分。使用者做自己的事情,資料也做自己的事情,不需要等待資料回傳的時間而在哪邊空等,這就是非同步請求。
J
AJAX 的 J 是「Javascript」,用來建立可以被嵌入或包含在 HTML 文件裡以及與 DOM 互動的函式。將新的內容載入至頁面部分區段中的能力,可改善使用者的操作經驗,因為若頁面只有少部分資訊需要更新,使用者就不需要再等待整個頁面的重新載入。(一頁式頁面因此崛起)。
A
AJAX 的 J 與 X 中間的 a 是「And」,BJ4。
X
AJAX 的 X 是「XML」(eXtensible Markup Language)擴充標記語言,一種儲存資訊的規格(在 JSON 出現前),也是一種描述資訊結構的規格。雖然 XML 是標記語言(就像 HTML),可說是兩兄弟,但是 XML 沒有自己的標籤,但它允許撰寫 XML 的人建立自己需要的標籤。XML 被用來格式化資料以利傳輸。而 HTML 被用來標記結構與內容。
jQuery 的 $.ajax() 方法
$.ajax()方法是 jQuery 最完整的 AJAX 方法,所有 jQuery 的 AJAX 快捷方法,都可以使用它做到,它允許在發送 AJAX 請求時,有更多精細的控制及調整。並搭配不同的控制設定以達成目的。它提供非常多,超級多的設定允許我們控制 AJAX,也是整個程式庫中最為複雜的函式。但使用它很簡單,一般比較直覺是傳入一個選項物件,此物件屬性可以設定 AJAX 所有細節。
$.ajax( PlainObject_settings )
jQuery.ajax({ |
選項物件的屬性設定
url
類型:String
預設:目前頁面
API 位置,要擷取資料的 URL,對 GET 請求而言,data 屬性傳送資料會被附加到這個 URL。
type(all version) or method(version 1.9+)
類型:String
預設:’get’
http 請求方法,預設為 get(讀取),其他常用請求方法為 post(新增) 或 patch(更新部分)、put(更新全部)、delete(刪除),請求方法有非常多種,請參考MDN。
dataType
類型:String
預設:Intelligent Guess(自動判斷)
預期伺服器回傳的資料型別。如果不指定,jQuery 將自動根據 HTTP 包 MIME 資訊返回 responseXML 或 responseText,比如 XML MIME 類型就被識別為 XML,並作為回呼函式引數傳遞。
| 資料類型 | 說明 |
|---|---|
| “text” | 回傳純文本字串,不做處理。 |
| “html” | 此類型就像 text 回應為純文字。load()方法使用這個類型,並將回傳的文字插入到文件本身。 |
| “xml” | 回傳 XML 文檔,可用 jQuery 的選擇器來遍歷處理。 |
| “script” | 把響應的結果當作 JavaScript 執行。並將其當作純文本回傳。 |
| “json” | 回傳 JSON 格式的資料。傳入 callback 的值是使用 jQuery.parseJSON()解析 URL 內容後所獲得物件。 |
| “jsonp” | 以 JSONP 的方式載入 JSON。 |
data
類型:PlainObject or String or Array
傳送到伺服器端的資料,將自動轉換為請求字串格式。GET 請求會附加在 URL 後。檢視 processData 屬性說明以禁止此自動轉換。 data 必須為 Key/Value 格式。如果為陣列,jQuery 將自動為不同值對應同一個名稱,如 {foo:[“bar1”, “bar2”]} 轉換為’&foo=bar1&foo=bar2’。要附加到 URL 中(GET 請求)或是要放在請求主體中送出(POST 請求)的資料,這可以是個字串或一個物件。物件通常會被轉為字串。
選項物件的回呼函式
$.ajax() 提供了幾個時機使用的回呼函式。
success
類型:function(Object data,String textStatus,jqXHR)
當 AJAX 請求成功去取得回應後,須執行的回呼函式。
第一個引數是由伺服器送出的資料,類型取決於 dataType 選項或伺服器回應的 Content-Type 選項。如果類型為”xml”,第一個引數就會是個 Document 物件。
第二個引數是 jQuery 狀態碼。
第三個引數是用以發出請求的 XMLHttpRequest 物件。
如果類型是 “json”或”jsonp”,第一個就是解析自伺服器的 JSON 格式回應的物件。
如果類型為”script”,回應就是已載入的指令碼的文字(不過屆時該指令搞已經被執行過了,在這個情況中回應通常可被忽略)。
對其他類型而言,回應單純就是所請求資源的文字。第二個引數狀態碼通常是字串”success”,如果已設定 ifModified 選項,這個引數可能會是”notmodified”。在這個情況下,伺服器不會送出回應,而第一個引數會是 undefined。”script”與”jsonp”類型的跨網域請求是透過 script 元素是非 XMLHttpRequest 來進行,因此對這些請求而言,第三個引數會是 undefined。
error
類型:function(jqXHR, String textStatus, String errorThrown)
當 AJAX 請求發生錯誤後,須執行的回函式,可能 HTTP 錯誤或其他因素。要找到更多資訊,可檢查 XMLHttpRequest 物件中的 HTTP 狀態碼,它可以傳入三個參數。
第一個引數是該請求的 XMLHttpRequest 物件(若是它有使用的話)。
第二個引數是 jQuery 狀態碼。對 HTTP 錯誤來說會是”error”,逾時則是”timeout”,而解析伺服器回應時發生的錯誤則是”parsererror”。
舉例來說,如果一份 XML 文件或是一個 JSON 物件的格式不正確,狀態碼就會是”parsererror”。在這種情況下,error callback 的第三個引數會是擲出的 error 物件。
注意 dataType 為 "script" 的請求傳回不正確的 JavaScript 程式碼時並不會導致錯誤。指令碼中任何的錯誤都會被無聲地忽略掉。而被呼叫的會是 success callback 而非 error callback。
complete
類型:function(jqXHR,textStatus)
不管成功或錯誤,只要請求完成就執行的回呼函式,請求 success 和 error 之後都會呼叫。此函式有 2 個參數,「jqXHR 物件」以及「包含成功或錯誤代碼的字串」。
jqXHR 物件
甚麼是 jqXHR 物件?直接用開發者工具瀏覽。
let jqXHR = $.get('https://kktix.com/events.json', function (res) { |
在 jQuery 1.5 版後,所有 jQuery 的 AJAX 方法 ($.get, $.post, $.ajax, …) 都會回傳一個 jqXHR 物件,jqXHR 是 XMLHTTPRequest 的超集合 (superset),紀錄了需要指定資料後續處理的方式,以較簡單的方式處理遠端伺服器所回傳的資料。
| jqXHR 特性 | 說明 |
|---|---|
| responseJSON | |
| responseText | 回傳以文字為基礎的資料 |
| status | 狀態碼 |
| statusCode | |
| statusText | 狀態碼說明(通常用於發生錯誤時,顯示錯誤相關資訊以利偵錯) |
| readyState | |
| setRequestHeader(name, value) | 通過替換舊的值為新的值,而不是替換的新值到舊值 |
| getAllResponseHeaders() | |
| getResponseHeader() | |
| abort() |
另外,從上圖可以知道,jqXHR 同時實作了 Promise 的介面 (interface),讓我們可以更方便操作非同步的 AJAX 請求。它擁有了 deferred 物件的方法,因此可以使用 .done()、.fail()、.always()、.then()這些方法,更方便進行串接 (chaining):
| jqXHR 方法 | 說明 |
|---|---|
| .done() | 當資料請求成功時,須執行的程式區段,替代了過去的.success() |
| .fail() | 當資料請求失敗時,須執行的程式區段,替代了過去的.error() |
| .then() | 兩個 Callback Functoin 參數,請求成功執行第一個 CB,請求失敗執行第二個 CB |
| .catch() | |
| .always() | 不管資料請求成功或失敗時,都會執行的程式區段,替代了過去的.complet() |
jqXHR.success()、jqXHR.error()和 jqXHR.complete()從 jQuery 3.0 移除。請使用 jqXHR.done(), jqXHR.fail(),和 jqXHR.always()代替。
// jqXHR 可以串接多個處理函式 |
Deferred 物件
jQuery 的 Promise 是 Deferred 物件,Deferred 物件遵循 CommonJS Promises/A 設計規範,可以將非同步轉變為同步的操作。參考官網。
創建 Deferred 物件
| 方法 | 說明 |
|---|---|
| $.Deferred() | 建立一個新的 Deferred 物件 |
let def = $.Deferred() // 建立一個 Deferred 物件 |
打印出來可以一覽 deferred 物件的全貌,它提供了操作 Promise 時熟悉的方法 done()、fail()、then()等,以及資料處理狀態成功 resolve()或失敗 reject()。
Deferred 的狀態
| 方法 | 說明 |
|---|---|
| deferred.resolve(arg) | 狀態成功要傳遞的資料 |
| deferred.reject(arg) | 狀態失敗要傳遞的資料 |
| deferred.promise() | 回傳狀態資料 |
jQuery 的 deferred 物件本身有 resolve()、reject() 方法,可以直接呼叫使用,規範中的 promise 則必須要傳入 resolve、reject 參數,這是它們的不同點,另外,遵循 promise 規範,resolve 與 reject 只能活一個,就像告白一樣,要不就解決,要不就被拒絕。並且提供了 promise()方法,此方法會確實回傳 resolve() 或 reject() 狀態的資料,避免被其他方法修改,這是一種隔離保護的封裝作用,它只能接受用 done()、fail()、then()、always()這些方法來處理它。以下為骰子骰出偶數為「解決」,骰出奇數為「拒絕」。
let def = $.Deferred() |
沒有在 resolve()、reject()中稱為第三狀態 pending,表示仍在進行中的狀態。
Deferred 進行中的狀態
當 Deferred 物件尚未設定為 resolved 或 rejected 狀態時,表示仍在進行中,則可使用 notify() 處理進行中的資料,使用方式與 resolve()與 reject()一樣。progress()方法使用與 done()一樣,可以用來追蹤進行中資料狀態的結果。
| 方法 | 說明 |
|---|---|
| deferred.notify() | 狀態進行中的資料 |
| deferred.progress() | 進行中狀態資料的結果 |
function replyProgress() { |
Deferred 狀態檢測
| 方法 | 說明 |
|---|---|
| deferred.state() | 回傳字串 “resolved”、”rejected” 或 “pending” |
Deferred 狀態結果方法
| 方法 | 說明 |
|---|---|
| deferred.done() | 完成時要處理的動作 |
| deferred.fail() | 失敗時要處理的動作 |
| deferred.then() | 具有完成時、失敗時、進行時三種狀態的動作 |
| deferred.always() | 不館完成或失敗都要處理的動作,類似 ajax 中的 complete |
| deferred.catch() |
done() 與 fail()分別要做的事情。
one() |
處理動作的方法也可以加入多個進行串鍊。
function f1() { |
在 Promise 規範中,then() 方法接受兩個參數,分別是執行完成和執行失敗的回調,而 jquery 中 deferred 進行了增強,接受第三個參數,在進行中狀態時的函式處理。
deferred.then( [doneFilter ] [, failFilter ] [, progressFilter ] )
// 與 .done().fail() 一模一樣的功能 |
從jQuery 1.8 開始,deferred.pipe() 列入不推薦使用,應該使用 deferred.then() 代替它。
$.when()
jquery 中,還有一個 $.when 方法來實現 Promise,與 ES6 中的 all 方法功能一樣,它可以合併多個 Deferred 物件,處理多個非同步任務,但任何一個回傳 rejected 就會進入 fail。必須在所有的非同步操作執行完後才執行回呼函式。然而,jQuery 中沒有像 ES6 中的 race 方法嗎?也就是以跑最快為準的方法。是的,答案是沒有。
| 方法 | 說明 |
|---|---|
| $.when() | 合併多個 Deferred 物件 |
// 成功接受三個 deferred 物件,才會執行後方 then 的回呼函式 |
$.when 並沒有定義在 $.Deferred 中,請看他們的名字,都是jQuery金錢工廠設計出的方法,$.when 是一個單獨的方法。與 ES6 的 all 的參數稍有區別,它接受的並不是陣列,而是多個 Deferred 物件象。
使用 callback 達到同步
在使用 callback 操作同步時,它將難以閱讀,而且很容易進入回呼地獄。
// callback |
使用 Deferred (promise)
function first() { |
AJAX 與 Deferred
// jQuery 的 Deferred() 就是在處理 promise |
狀態碼
待補
跨來源資源共用(CORS)
待補
ajax 其他各種工具
jQuery 定義了一個高階工具方法,以及四個高階的工具函式。這些高階工具全部基於單一個威力強大的低階函式:$.ajax()。以下只需要了解即可:
load() 方法:
.load()方法可以載入部份內容,.load()方法是 jQuery Ajax 方法中最容易使用的方法。它只能用於自伺服端載入 HTML 內容。當伺服回應時,HTML 內容便會載入至 jQuery 選取集合中。你只要傳入一個 URL 給它,就會非同步地載入那個 URL 的內容,然後將內容插入至每個所選元素中,取代任何原有內容。此函式預設是以 GET 的方式來發送請求,但是如果有設參數 data 則會自動轉為 POST。
.load( url [, data ] [, complete ] )
// 載入 jq-ajax3.html #content 部分內容 |
// 每 60秒 載入並顯示最新的狀態報告 |
如果 load()方法第一個引數是個函式而非字串,會被當作 load 事件註冊處理器,而非一個 Ajax 方法。
第二參數如果傳入一個字串,會被附加到 URL(視狀況在?或&之後)。如果傳入一個物件,會被轉成以&區隔的 name=value 對組所成的字串,然後連同請求一起送出。load()方法通常是發出 HTTP GET 請求,如果傳入一個資料物件,它會改發出 POST 請求。
// 指定郵遞區號字串作為資料 |
$.get()與 $.post()
$.get()與$.post() 擷取指定 URL 的內容,傳遞指定的資料(如果有的話),並將結果傳給指定的 callback。$.get()透過 HTTP GET 請求來達到此目的,而 $.post()則使用 POST 請求,兩個工具函式是相同的。
$.post( url [, data ] [, success ] [, dataType ] )
這兩個方法取的四個引數:
- 一個必要的 URL。
- 一個非必須的資料字串或 Javascript 物件實字。
- 一個幾乎每次都會用到的 callback 函式。這個 callback 函式第一個引數是回傳的資料、第二個引數是字串”success”,第三引數是 XMLHttpRequest(若有的話)。
- 接受第四個非必須的引數(如果省略資料的話,就作為第三引數傳入)用來指定請的資料類型。這第四個引數會影響資料傳到你的 callback 之前被處理的方式。
$.get()、$.post() 與 PHP 使用時
可以直接傳物件實字給 get()或 post()函式,下面使用 query string 方法,它們經常出現在 URL 的 ? 後面。
例如:http://www.example.com/rankMovie.php?rating=5
// 使用字串方式 |