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: url, // API位置,要擷取資料的 URL
type: 'get', // HTTP 請求方法
dataType: 'json', // 預期從伺服器回傳的資料型別
data: { a: 123 }, // 傳甚麼資料過去 Object、String、Array
cache: false, //暫存(cache)在本地,以減少對伺服器的呼叫。
success: function () {
// 完成時呼叫這個函式
}
})

選項物件的屬性設定

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) {
console.log(res)
})
console.log(jqXHR)

在 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 可以串接多個處理函式
// 並把函式返回的 jqXHR 存回 jqxhr 變數
let jqxhr = $.ajax('example.php')
.done(function () {
alert('成功')
})
.fail(function () {
alert('失敗')
})
.always(function () {
alert('結束')
})

// .done() .fail() .always() .then() 都可以重複 call 很多次
// 所有 callback 都會依序的執行
jqxhr.always(function () {
alert('結束 part 2')
})

Deferred 物件

jQuery 的 Promise 是 Deferred 物件,Deferred 物件遵循 CommonJS Promises/A 設計規範,可以將非同步轉變為同步的操作。參考官網

創建 Deferred 物件

方法 說明
$.Deferred() 建立一個新的 Deferred 物件
let def = $.Deferred() // 建立一個 Deferred 物件
console.log(def) // 打印出來看看

打印出來可以一覽 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()
function one() {
setTimeout(function () {
let rnd = Math.floor(Math.random() * 6) + 1
console.log(rnd)
rnd % 2 === 0 ? def.resolve('骰出偶數,解決') : def.reject('骰出奇數,拒絕')
}, 1000)
return def.promise() // 最後要回傳狀態,成功或失敗
}

one()
.done(function (res) {
console.log(def.state())
console.log(res)
})
.fail(function (res) {
console.log(def.state())
console.log(res)
})

沒有在 resolve()、reject()中稱為第三狀態 pending,表示仍在進行中的狀態。

Deferred 進行中的狀態

當 Deferred 物件尚未設定為 resolved 或 rejected 狀態時,表示仍在進行中,則可使用 notify() 處理進行中的資料,使用方式與 resolve()與 reject()一樣。progress()方法使用與 done()一樣,可以用來追蹤進行中資料狀態的結果。

方法 說明
deferred.notify() 狀態進行中的資料
deferred.progress() 進行中狀態資料的結果
function replyProgress() {
let def = $.Deferred()
for (let i = 1; i <= 5; i++) {
setTimeout(() => {
def.notify(i)
}, 1000 * i)
}
return def.promise()
}
let promise = replyProgress()
promise.progress(function (res) {
console.log(res)
})

Deferred 狀態檢測

方法 說明
deferred.state() 回傳字串 “resolved”、”rejected” 或 “pending”

Deferred 狀態結果方法

方法 說明
deferred.done() 完成時要處理的動作
deferred.fail() 失敗時要處理的動作
deferred.then() 具有完成時、失敗時、進行時三種狀態的動作
deferred.always() 不館完成或失敗都要處理的動作,類似 ajax 中的 complete
deferred.catch()

done() 與 fail()分別要做的事情。

one()
.done(function (res) {
console.log(def.state())
console.log(res)
})
.fail(function (res) {
console.log(def.state())
console.log(res)
})

處理動作的方法也可以加入多個進行串鍊。

function f1() {
console.log('f1')
}

function f2() {
console.log('f2')
}

function f3() {
console.log('f3')
}

one()
.done(f1, f2)
.done(function (res) {
console.log(def.state())
console.log(res)
})
.done(f3)
.fail(function (res) {
console.log(def.state())
console.log(res)
})

在 Promise 規範中,then() 方法接受兩個參數,分別是執行完成和執行失敗的回調,而 jquery 中 deferred 進行了增強,接受第三個參數,在進行中狀態時的函式處理。

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

// 與 .done().fail() 一模一樣的功能
one().then(
function (res) {
console.log(def.state())
console.log(res)
},
function (res) {
console.log(def.state())
console.log(res)
}
)

從jQuery 1.8 開始,deferred.pipe() 列入不推薦使用,應該使用 deferred.then() 代替它。

$.when()

jquery 中,還有一個 $.when 方法來實現 Promise,與 ES6 中的 all 方法功能一樣,它可以合併多個 Deferred 物件,處理多個非同步任務,但任何一個回傳 rejected 就會進入 fail。必須在所有的非同步操作執行完後才執行回呼函式。然而,jQuery 中沒有像 ES6 中的 race 方法嗎?也就是以跑最快為準的方法。是的,答案是沒有。

方法 說明
$.when() 合併多個 Deferred 物件
// 成功接受三個 deferred 物件,才會執行後方 then 的回呼函式
$.when(def1(), def2(), def3()).then(function (data1, data2, data3) {
console.log('全部執行完成')
console.log(data1, data2, data3)
})

$.when 並沒有定義在 $.Deferred 中,請看他們的名字,都是jQuery金錢工廠設計出的方法,$.when 是一個單獨的方法。與 ES6 的 all 的參數稍有區別,它接受的並不是陣列,而是多個 Deferred 物件象。

使用 callback 達到同步

在使用 callback 操作同步時,它將難以閱讀,而且很容易進入回呼地獄。

// callback
function first() {
console.log(1)
}
function second(callback) {
setTimeout(() => {
console.log(2)
callback()
}, 0)
}
function third() {
console.log(3)
}
first()
second(third)

使用 Deferred (promise)

function first() {
// 使用 $.Deferred() 物件
let def = $.Deferred()
console.log(1)
// resolve 或 reject
def.resolve('第一個完成')
// def.reject('第一個失敗')
return def.promise()
}
function second(res) {
let def = $.Deferred()
setTimeout(() => {
// 接收第一個函式的參數
console.log(res)
console.log(2)
// resolve 或 reject
def.resolve('第二個完成')
// 可以傳出錯誤的訊息,不一定是字串,可以為任何資料型態
// def.reject('第二個錯誤')
}, 0)
return def.promise()
}
function third(res) {
let def = $.Deferred()
console.log(res)
console.log(3)
// resolve 或 reject
def.resolve('最後一個步驟完成')
// def.reject('最後一個步驟失敗')
return def.promise()
}

// 呼叫
first()
.then(second)
.then(third)
.then(
// 成功
function (ok) {
console.log(ok)
},
// 失敗
function (err) {
console.log(err)
}
)

AJAX 與 Deferred

// jQuery 的 Deferred() 就是在處理 promise
;(function () {
function first() {
// 使用 $.Deferred() 物件
let def = $.Deferred()
console.log(1)
// resolve 或 reject
def.resolve('第一個完成')
// def.reject('第一個失敗')
return def.promise()
}
function second(res) {
let def = $.Deferred()
console.log(res)
// ajax({url,type,dataType,data,successCallback})
// API位置,呼叫方式,回傳資料類型,傳過去的資料
$.ajax({
url: './data.json',
type: 'get', // method: get,post,put,path,delete
dataType: 'json', // 資料格式:json,html,xml,text,jsonp(alex不怎麼愛)
data: { a: '123' } // 傳甚麼東西過去,Object、String、Array
}).then(
function (res) {
// response 回應 <-> request 要求
console.log(res)
return def.resolve(res)
},
function (err) {
console.log(err)
return def.reject(err)
}
)
return def.promise()
}

function third(res) {
let def = $.Deferred()
console.log(res)
console.log(3)
// resolve 或 reject
def.resolve('最後一個步驟完成')
// def.reject('最後一個步驟失敗')
return def.promise()
}

// 呼叫
first()
.then(second)
.then(third)
.then(
// 成功
function (ok) {
console.log(ok)
},
// 失敗
function (err) {
console.log(err)
}
)
})()

狀態碼

待補

狀態碼參考

跨來源資源共用(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 部分內容
$('#content').load('jq-ajax3.html #content')
// 每 60秒 載入並顯示最新的狀態報告
setInaterval(function () {
$('#status').load('status_report.html')
}, 60000)

如果 load()方法第一個引數是個函式而非字串,會被當作 load 事件註冊處理器,而非一個 Ajax 方法。

第二參數如果傳入一個字串,會被附加到 URL(視狀況在?或&之後)。如果傳入一個物件,會被轉成以&區隔的 name=value 對組所成的字串,然後連同請求一起送出。load()方法通常是發出 HTTP GET 請求,如果傳入一個資料物件,它會改發出 POST 請求。

// 指定郵遞區號字串作為資料
$('#temp').load('us_weather_report.html', 'zipcode=02134')
// 用物件做為資料
$('#temp').load('us_weather_report.html', { zipcode: 02134, units: 'F' })

$.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

// 使用字串方式
$.get('rankMovie.php', 'rating=5')
// 將上面重新改寫成物件實字
$.get('rankMovie.php', { rating: 5 })

// 或是存在變數中
let data = { rating: 5 }
$.get('rankMovie.php', data)