此系列主要討論 JavaScript 發送請求的方式,
若對 XMLHttpRequest 不熟稔,建議閱讀 上集。
就算不會 JavaScript/jQuery,閱讀本篇後,您將擁有基本實作能力,
不清楚如何執行 JavaScript 的話,可參閱 W3SChools,或利用 CodePen 進行測試。
目錄
jQuery Ajax
jQuery [dʒeˋkwɪrɪ] 是快速、小巧、功能豐富 的 JavaScript 函式庫,
然而,前端的快速發展, React、Angular、Vue …等框架的出現,
讓我們不再依賴 DOM、Event,自然也漸漸不需 jQuery。
例如,Vue 的 響應式系統 (reactivity system),
將資料 綁定 (binding) 到 DOM 的文本或結構,
讓我們能專注在底層邏輯,而非冗余的操作:
儘管 jQuery 被認為過時,而且我也很少用 😂,
但說過啦 : 仍值得了解其用法,
對於理解 其他 API、維護舊系統 皆能有所幫助。
別擔心,本篇重點不在 jQuery 過時與否,
而是 jQuery Ajax 的使用 與 非同步概念 (ES6 Promise, ES7 Async/Await),
因此,就算您不使用 jQuery,也能有所收穫 …吧 😂。
基本使用
同樣的,以 jQuery 對 https://gank.io/api/random/data/福利/20,
送出 GET 請求,並將回應提供的 影像 URI,添加至頁面為例:
安裝
安裝方式 非常多種,這裡我使用最簡易的 Max CDN :
(在 HTML 的 head 元素內加入 <script src=…)
<head>
<title>jQuery-Ajax-Demo</title>
<meta charset="utf-8">
<script src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
</head>
jQuery.get()
首先,呼叫 get 方法,參數放置的是 目標 URI,
其後伴隨著 done() 方法,設置回應完成後要做的事 — 回調函式 (callback) :
(當然,還有還有其他實例方式 — — 可選參數、多載函式…,詳見 官網)
$.get("https://gank.io/api/random/data/福利/20")
.done(res => jsonHandler(res));
(此例沿用上集的 jsonHandler 函式)
[註]:
上例為使用 箭頭函數 (Arrow Function) 的寫法,相當於:
$.get( "https://gank.io/api/random/data/福利/20")
.done(function(res){jsonHandler(res)});
[註]:
「$」是 jQuery 全域函式的捷徑符號
完成 ! (一切都是為了學習),
可前往 這裡 觀看成果 :
是否覺得超快、超方便 😂?
實際上 jQuery 就是把 XMLHttpRequest (XHR) 再包一層 !
並幫你處理許多麻煩的事情 (e.g., 請求生命週期、回應解析)。
Promise
如果只是簡單的封裝,構不成 jQuery Ajax 屌壓 XMLHttpRequest 的理由,
重要的是 jQuery 在 1.5 版時,實作了 Promise 語法結構,
且在 3.0 版時,已相容於 Promises/A+ 標準規範,
藉此擺脫可怕的 回調函式地獄 (Callback Hell) !
Promise 是一個 非同步/異步 (asynchronous) [eˋsɪŋkrənəs] 操作的執行 結果,
它使 jQuery 能輕易在單個請求上分配多個 回調函式 (callback),甚至在請求完成後才分配。
而 Promise 在 jQuery 中的實作者即 Deferred 物件,
與其子裔 — — jqXHR 物件 。
方法鏈
標準的 Promise 共有三種狀態 (MDN):
- pending: 等待中,為初始之狀態,即不是 fulfilled 也不是 rejected。
- fulfilled: 已實現,表示操作完成,又稱 resolved。
- rejected: 已拒絕,表示操作失敗。
[註]:
此處對 MDN 示意圖 做了些微簡化。
推薦閱讀 從Promise開始的JavaScript異步生活 了解更多 😄。
穩定 (settled) 代表的是:
Promise 只會 完成 或 拒絕 一次,
且不會再改變 (完成變拒絕 or 拒絕變完成) 。
因此 jqXHR 物件 每次執行方法,都會回傳 新的 Promise 物件,
且 根據 非同步的執行 狀態,執行 對應的處理。
直接看例子:
$.get("qq")
.fail(function () {
alert("error1");
})
.fail(function () {
alert("error2");
})
.done(function () {
alert("success");
})
.always(function () {
alert("complete");
});
請求 成功 則 (done) 顯示: “success” 與 “complete”,不會 顯示 “errorX” ;
請求 失敗 則 (fail) 顯示: “error1” 與 “error2” 與 “complete”,不會 顯示 “success” 😲 !
以下為 jQuery 常用的 jqXHR 方法 (大部分為擴展的非標準方法) :
- done(x) — 當 jqXHR 狀態為 Resolved 才執行 x,並回傳新的 Resovled jqXHR。
- fail(x) — 當 jqXHR 狀態為 Rejected 才執行 x,並回傳新的 Rejected jqXHR。
- always(x) — 總是執行 x,並回傳新的 Resovled jqXhr。
- … (請參考 官方文件)
Async/Await
儘管 ES6 的 Promise,已大大改善 非同步 的寫法,
(ECMAScript 為 JavaScript 標準)
回調函式 (callback) 的影子還是很重… 😥。
於是 ES7 推出了 神器 :
Async/Await 仍處於草案, 卻已被稱為 非同步的終極解決方案,
在開始之前,請再瞄一下 Promise 狀態圖 :
async function
async/await 用法是以 關鍵字 async function 宣告一個 非同步函式,
並在 非同步函式 內部,加上 await 運算式,以等待 Promise 的解析 :
async function test(param) {
// await 會暫停此 async function 的執行,
// 並等待 doSomethingAsync() 回傳 Promise 解析 (Resolved) 的 『值 (Value)』
// 若 Promise 解析失敗 (被拒絕 rejected) 則拋出例外,並附加 『拒絕理由 (reason) 』
var data = await doSomethingAsync();
// Promise 解析完成之後,會繼續此 async function 的執行
console.log(data);
}
使用範例
覺得太文言文,就看個例子吧 !
一個簡易的 同步函式,參數可以帶入布林值,
代表直銷拉人 成功 or 失敗,並回傳 Promise 的解析結果 :
function 你聽過安利嗎(flag) {
return new Promise(function (resolve, reject) {
var value = "一起年薪百萬啦 !";
var reason = "先來用我家 賀寶寶奶昔 啦 !";
if (flag) {
// 解析/接受/完成 Promise 並回傳 value
resolve(value)
} else {
// 拒絕 Promise 並回傳 拒絕 reason 理由
reject(reason);
}
})
}
接著,一個 非同步函式 test ,
用 await 等待拉人的結果 🤓 :
async function test() {
console.log("測試 1. 拉人成功:");
let test = await 你聽過安利嗎(true);
console.log("Result: " + test);
console.log("-----------------\n");
console.log("測試 2. 拉人失敗:");
test = await 你聽過安利嗎(false);
console.log("Result: " + test);
}
開始執行 (請注意 第三行的小陷阱) !
console.log("測試開始:");
test();
console.log("測試結束了 ?");
想想結果吧 🤔
時間到 ~ Result:
" 測試結束了 ? " 出現在第三行,是因為執行 test() 時,
遇到了 : await 你聽過安利嗎(true);
因此暫停 async function 的執行,優先執行 同步 的 "測試結束了 " !
async function test() {
console.log("測試 1. 拉人成功:");
let test = await 你聽過安利嗎(true);
...
接著,等待回傳 Promise 解析 (Resolved) 的 『值 (Value)』:
if (flag) {
// "一起年薪百萬啦 !"
resolve(value)
...略...
若 Promise 解析失敗 (被拒絕 rejected) 則拋出例外,並附加 『拒絕理由 (reason) 』
// "先來用我家 賀寶寶奶昔 啦 !"
reject(reason);
非 Promise
更狂的是,await 之後所接的內容,不一定要實作 Promise,
await 會透過 Promise.resolve() 解析它 !
這代表 : 你能安全地在任何 預期等待的地方 使用 await !
例如,此非同步函式,『20』並非一個 Promise 物件,
因此直接轉換其值為 resolved Promise,並等待之 (by MDN) :
async function f2() {
var y = await 20;
console.log(y); // 20
}
f2();
對喔 ! 你好棒棒 :
async/await 是操作 Promise 的 語法糖 (syntactic sugar) [sɪnˋtæktɪk] !
瀏覽器相容性
需注意的是許多瀏覽器 (尤其是行動裝置) 尚未支援 Promise 與 Async Function,
可以使用 Babel 等編譯器,轉換為相容的程式碼喔 !
詳見 Babel 安裝 與 補完函式庫 (Polyfill) 教學。
改寫 promise 鏈
MDN 的 範例說明 寫得太好了 ! 這裡擷取部分內容說明,
強烈建議,對 async/await 有興趣的話一定要看看 !
回調函式地獄
以往的 (ES5) 非同步運算,容易墮入可怕的「金字塔惡夢」… :
Promise
使用 Promise (es6) 後,蘇湖啊 !
箭頭函式 (Arrow Function)
結合 箭頭函式 後,又進化了 :
Async/Await 降臨
最後,終極解決方案 async/await :
不難發現,async/await 最強大之處在於:
能以 同步 的流程,敘述 非同步/異步 的操作 !
這不僅僅是簡潔的層次了,
它讓 非同步 返璞歸真,能用最直覺的方式撰寫程式 !
當然,採用何種方式見仁見智,選個最舒服的吧 😄 。
改寫範例
使用 async/await 改寫最初的範例:
// 使用關鍵字 async 宣告 非同步函式
async function enjoy() {
// 等待獲取回應
var data = await $.get("https://gank.io/api/random/data/福利/20");
// 回應 (Promise) 完成時,自動執行此行
jsonHandler(data)
}
Result:
async/await 萬歲 !
POST 請求
jQuery Ajax 要送出 POST 請求,
僅需將 請求方法 換成 $.post(),並 置放資料 (如果有的話) 即可,
第一個參數放置的是 目標 URI,後伴隨著欲發送的 資料酬載 (payload):
$.post("demo_test_post.asp", {name: "勝", city: "Taipei"})
.done(function (data) {
console.log("success");
document.body.innerHTML = data;
})
.fail(function (jqXHR, textStatus, errorThrown) {
console.error(errorThrown);
})
.always(function () {
console.log("finished");
});
請求成功即顯示: “success” + “finished”,
請求失敗則顯示: “error” + “finished”,
請至 W3schools 嘗試。
當然,可以使用 async/await 改寫 :
async function post(url) {
try {
var data = await $.post(url, {name: "勝", city: "Taipei"});
console.log("success");
document.body.innerHTML = data;
} catch (jqXHR) {
console.error(jqXHR.statusText);
}
console.log("finished");
}
是否覺得邏輯更清晰呢 🤔 ?
內容類型 (Content-Type)
(延續上例)
咦 ! 不用處理 酬載 的編碼嗎 !?
Ans:
可能不用
XHR vs. jQuery
不同於 XMLHttpRequest,jQuery 請求的 編碼方式 (enctype) 預設即為:
application/x-www-form-urlencoded; charset=UTF-8
且會 自動 為資料進行 百分比編碼 (Percent-Encoding) 喔 !
讓我們看一下,一樣的資料:
var data = {name: "勝", city: "Taipei"};
以 jQuery.post() 送出,已 自動編碼 :
name=%E5%8B%9D&city=Taipei
以 xhr.send(data) 送出,這是尛啦 :
[object Object]
儘管如此,還是有許多機會需要手工處理 資料酬載 (payload) 喔 !
例如,常見的 JSON.stringify()
方法。
ajax() 方法
包含常使用的 $.get()、$.post(),
另外還有 getJSON()、getScript()、load(),共五種 快捷方法 (Shorthand Methods),
其實最後都是呼叫較低階的 API — — $.ajax() 方法,格式為:
// 回傳 jqXHR 物件 (實作 Promise)
jQuery.ajax( url [, settings ] )
例如,欲使用 PUT 方法傳送 JSON 資料時,
需變更 method
與 contentType
屬性:
$.ajax({
url: "user.php",
method: "PUT",
contentType: "application/json",
data: JSON.stringify({
"name": "Jason",
"id": 1,
"handsome": true
})
})
.done(function (data, textStatus, jqXHR) {
console.log("success");
console.log(data);
})
.fail(function (jqXHR, textStatus, errorThrown) {
console.log(errorThrown);
})
.always(function () {
console.log("finished");
});
使用 jQuery 1.9.0 以前的版本,需將 method 以 type
屬性替換,
詳盡的設定列表,請參閱 官方文件。
使用 async/await 改寫 :
async function ajax(setting) {
try {
var data = await $.ajax(setting);
console.log("success");
console.log(data);
} catch (jqXHR) {
console.error(jqXHR.statusText);
}
console.log("finished");
}
ajax(setting);
封裝 XHR
既然 jQuery 能 封裝 XMLHttpRequest (XHR) 為 Promise,那我們呢?
Ans:
當然也可以
function GET(url) {
// 使用 Promise() 建構元 建立 promise 物件
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function () {
if (200 <= xhr.status && xhr.status <= 299) {
// 解析/接受/完成 Promise 並回傳 回應 value
resolve(xhr.responseText);
}
else {
// 拒絕 Promise 並回傳 拒絕 reason 理由
reject(Error(xhr.statusText));
}
};
xhr.onerror = function () {
reject(Error("Network error."));
};
xhr.send();
});
}
於是,能如此使用 :
async function enjoy() {
let res = await GET('https://gank.io/api/random/data/福利/20');
let data = JSON.parse(res);
jsonHandler(data)
}
Oops…😨 找不到用 jQuery Ajax 的理由了…
範例原始檔: Github,
下集: (5-7) 發送 HTTP 請求 (III) Fetch API。
在《jQuery Ajax — JavaScript 發送 HTTP 請求 (II)》中有 6 則留言
推
這篇太神了!
深入淺出的介紹
所有前端開發都會遇到的 ajax
也更瞭解 ES6 的重點 promise
大推!
感謝支持 😃
海馬 v 法老王…我笑歪😂
推 Ajax 看了很多文章
第一篇把前世今生說清楚了 3Q