ajax

寫在前面

AJAX 全名為Async Javascript and XML,是常見的網頁技術,目的在取得資料的時候,不需要整個網頁重新載入,可以讓使用者體驗更加流暢,也是我們常聽到呼叫api 的實際執行方式。

較常見的寫法如透過jQuery 的ajax來執行,也是這篇筆記的紀錄重點,其他還有axios, fetch 等工具可以作為HTTP的請求工具。
會想記錄這個主題,是因為最近在使用jQuery.ajax 時,多使用到了一個之前沒用過的參數,為避免忘記,趕快筆記起來。
更仔細的說明可以參考:
jQuery 說明文件

以下正文

jQuery ajax

假設前端頁面需要訪問 sample 這個地址,取得相對應的資料並顯示在頁面上,sample 就是api 的 endpoint。
為了避免畫面重新載入,讓使用者有更好的體驗,我們透過 jQuery.ajax 作為呼叫api 的工具,overlay 則是我們先準備好的一個loading畫面,
當開始進行訪問時就顯示在畫面上,除了可以讓使用者知道資料正在載入外,也可以避免連點而被多次觸發。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 使用 $.ajax() 方法
overlay.show(); // loading...
$.ajax({

// 進行要求的網址(URL)
url: 'sample',

// 要送出的資料 (會被自動轉成查詢字串)
data: {
id: 'a001'
},

// 要使用的要求method(方法),eg: POST, GET, PUT...
type: 'GET', // Default GET

// 預期response回傳的資料類型
dataType: 'json',

// 傳送到server時的資料型態
contentType: "application/json",

// 這次新用到的參數,在一般呼叫時可以不帶
context: {
prevent_alert: true
}
})
.done(function() {
// 要求成功時要執行的程式碼
// 回應會被傳遞到回調函式的參數
// 取得context 中的參數
if (!data.success && !this.prevent_alert) {
alert(data.message || data.error);
} else {
// do something if success
alert('已成功取得資料');
}
})
.failed(function(jqXHR) {
// 要求失敗時要執行的程式碼
// 狀態碼會被傳遞到回調函式的參數
// do something if error
switch (jqXHR.status) {
case 401:
location.href = "login-form/" + btoa(location.href).replace(/\+/g, "-").replace(/\//g, "_").replace(/\=/g, ""); // 表示沒有權限,導向登入頁面要求登入
return;
default:
alert(jqXHR.statusText);
}
})
.always(function() {
// 不論成功或失敗都會執行的回調函式
overlay.hide();
})

假設這次的api 呼叫是成功的,則可以根據需求,顯示在畫面上對應的位置,或是進行其他處理。
而這次我新使用到的參數—context,是主要要介紹的重點,

  • context
    這次新使用到的參數,可以在ajax 所有回調函式中,用this 來取得。
    可以放入dom元素,如 document.body
    在callback 中,如果成功則加上一個active class:$(this).addClass('active')
    也可以像上面的範例,傳入一個物件,透過this來取得內容:this.prevent_alert

因為我們在專案中的習慣,會把$.ajax 包進一個通用的function 中,所以需要針對特殊情形進行處理,為了在ajax 執行完成後,
可以判斷這次是否是需要alert錯誤訊息,因此我放了一個參數:prevent_alert,其值為true,當共用的函式取得此參數,且值為true時,就不進行alert。

會使用到context這個參數,是因為專案中有部分的表單,如果取得錯誤的訊息,會需要在表單上直接標出錯誤內容,所以不希望使用者收到兩次的錯誤訊息:
原本錯誤會alert(),又重複在表單上看到錯誤內容,因此 alert 就顯得多餘了。

ajaxSetup

也因為我們把$.ajax 包在function 中,因此我們會透過ajaxSetup,預先將基本且不會變動的參數都定義好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$.ajaxSetup({
contentType: "application/json",
error(jqXHR) {
switch (jqXHR.status) {
case 401:
location.href = "login-form/" + btoa(location.href).replace(/\+/g, "-").replace(/\//g, "_").replace(/\=/g, "");
return;
default:
alert(jqXHR.statusText);
}
},
success(data) {
if (!data.success && !this.prevent_alert) {
alert(data.message || data.error); // 造成前面提到重複alert的地方
}
},
type: "POST"
});

const api = (url, data, options) => {
let ajaxOptions;
if ($.isPlainObject(data)) {
ajaxOptions = {data: JSON.stringify(data), url}
} else {
ajaxOptions = {contentType: false, data, processData: false, url}
}
return $.ajax(Object.assign(ajaxOptions, (options || {})));
}

如此一來,在各個頁面中需要使用到$.ajax進行api 存取時,則改為使用api 這個function 來進行:

1
2
3
4
5
6
7
8
api("sample", {id: 'A001'}, {context: {prevent_alert: true}})
.done((data) => {
if (data.success) {
$(".shopping-cart-count").text(data.count);
} else if (data.type === "error") {
$("#error_message").text(data.message);
}
});

success vs done

從上面兩段程式碼可以看到,我們在setup 中定義了兩個callback: success, error
但是第一段範例,直接呼叫的時候則沒有定義這兩個callback,而是透過done, failed 來處理api 的結果,稍微去查了一些資料可以發現,
一開始ajax 的callback包含success、error、complete 等,是較傳統的ajax callback function,這些在ajax 呼叫時被定義在options 內;
而done、failed、always 則是 Deferred物件的方法的出現後,較好的實做方式。

所以在ajaxSetup 中,要處理後端回傳的錯誤訊息,我們還是可以預先定義success, error 的function,將呼叫成功但後端回覆錯誤,或是呼叫失敗的情況,
先在setup中做基本的處理,這是屬於ajax 的 option之一;
而實際使用$.ajax 的地方,則會透過 done 來處理呼叫完成的後續動作。

說到deferred物件,就會想到promise物件,這中間有一點雷同,也有一些差異,所以我會把promise的一些資訊,整理到下一篇筆記中。