[學習之路] Ajax 的相關領域技術與應用
Ajax 是「Asynchronous JavaScript and XML」(非同步的 JavaScript 與 XML 技術)的縮寫。我們知道靜態網頁都是一次性同步 (Synchronous HTTP Requests) 的請求與加載,每次要更新一個小部分都需要重新載入網頁。而透過 Ajax 能將網頁像電腦應用程式,不需要重新加載網頁情況下就能透過瀏覽器偷偷跟伺服器取得資料,並透過 DOM 技術更新一小部分的畫面。伺服器對 Ajax 資料請求回應通常是以三種資料格式其中之一(HTML、XML、JSON)
Ajax 使用非同步 (Asynchronous HTTP Requests) 方式,讓瀏覽器跟伺服器互相傳遞資料(可能是 string, html code, xml, json 格式),緊接著需畫面翻新時,直接透過 DOM 完成畫面調整。因此 Ajax 是指多種技術來接力完成行為:
- HTML/CSS: 先提供瀏覽器畫面讓使用者進行操作觸發
- XML/JSON/String/HTML: 伺服器採非同步方式將資料傳給瀏覽器之 JavaScript
- DOM: 瀏覽器將 HTML 進行新增修改刪除行為
- Asynchronous HTTP Requests: JavaScript 使用 HTTPRequest 物件 建立非同步的 HTTP 請求。
同步與非同步的網頁請求最大差異是:
- 同步 HTTP 請求會等待回應至整個網頁回來,因此網頁回應的時間內用戶不能做任何事情。每一次的網頁操作都是送出、等待、取得、顯示,才能做下一次操作。
- 非同步 HTTP 請求除了第一次的 HTTP/CSS 加載完成後,之後的每次操作透過 JavaScript 建立的 Aja 引擎來提出非同步請求,因此每次 Ajax 的動作都是獨立且非同步(對伺服器來說兩者不一致)且需要透過 DOM 來補足內容差異性。網頁可持續其他作業不需要等待網頁回應而受中斷。
四種 Ajax 技術方式
JavaScript 的 Ajax
JS 的 Ajax 應用需要透過標準物件 XMLHttpRequest()
來完成初始化。主要提供 GET 跟 POST 方法來取得或送出資訊。宣告完成後你隨時可以透過屬性 readyState 來檢查目前處理進度(提供代碼 0~4)。順序作業如下:
介紹 XMLHttpRequest
雖然 XMLHttpRequest 這個物件的命名包含了 XML 與 HTTP 等字眼,但實際上 XMLHttpRequest 可用來接收任何類型的資料,不限於 XML 類型而已。正式運作 Ajax 你需要先用變數完成函式建構。
- XMLHttpRequest():
建構式用來初始化一個 XMLHttpRequest 物件。必須在其他屬性或方法之前先宣告。 - readyState:
能知道目前的 HTTP 請求狀態value state Description 0 UNSENT 客戶端已被建立,但 open() 方法尚未被呼叫。 1 OPEND open() 方法已被呼叫。 2 HEADERS_RECEIVED send() 方法已被呼叫,而且可取得 header 與狀態。 3 LOADING 回應資料下載中,此時 responseText 會擁有部分資料。 4 DONE 完成下載操作。 - responseText:
回傳一個 DOMString,其內容為請求之回應的文字內容。如請求失敗或尚未發送,則為 null。 - responseXML:
回傳一個 Document,其內容為請求之回應內容所解析成的文件物件。如請求失敗或尚未發送,又或是無法解析成 XML、HTML,則為 null。 - status:
回傳一個無符號短整數(unsigned short)表示已發送請求之回應的狀態。通常是 200 或 404 資訊。 - statusText:
回傳一個 DOMString 表示 HTTP 伺服器回應之字串。和 XMLHTTPRequest.status 不同的是,XMLHttpRequest.statusText 包含了回應的整個文字訊息(如 “200 OK”)。 - onreadystatechange():
一個 EventHandler(事件處理器)函式,會於 readyState 屬性之狀態改變時被呼叫。 - abort():
中止已發出的請求。會將 readyState 重置歸 0。 - getAllResponseHeaders():
回傳所有的回應標頭(response headers),為一以斷行字元(CRLF)分行的字串,如未接收到回應(readystate!=4) 則為 null。 - getResponseHeader():
回傳指定標頭文字之字串,假如回應尚未被接收或是標頭不存在於回應中則為 null。例如指定 getResponseHeader(“Content-Type”) 則可能回傳 application/json; charset=UTF-8 或 text/plain; charset=UTF-8 等資訊。 - setRequestHeader():
設定 HTTP 請求標頭(request header)值。必須於 open() 之後、在 send() 之前。 - open():
初始化一個請求。此方法用於 JavaScript 中;若要在 native code 中初始化請求,請以 openRequest() 作為替代。open(
DOMString method, //GET or POST
DOMString url, //target URL
optional boolean async, // true(default is async) or false(sync)
optional DOMString user, //驗證用使用者名稱
optional DOMString password //
); - send():
發送請求。如果為非同步請求(預設值),此方法將在發出請求後便立即回傳(return)。
完整作業順序說明
- 宣告 XMLHttpRequest
let jsAjax = new XMLHttpRequest(); // 宣告 HTTP 請求之標準物件
console.log(jsAjax.readyState); // 0 => 已經產生一個 XMLHttpRequest,但是還沒連結要撈的資料 - 指定位置與送出
- GET 方式:通常用於直接拿資料(不送資料),如真有必要直接寫在 URL 編碼上來提供。
GET jsAjax.open("GET", "test.json", true); //設定方法與傳送位置,布林值預設為 true(非同步),可指定 true 來執行同步作業。
console.log(jsAjax.readyState); // 1 => 用了 open,但是還沒傳資料
jsAjax.send(); //傳送資料,沒有要送可以 null 不輸入 - POST 方式(以 Form-Data 傳送):與傳統表單一樣,需要特別宣告
setRequestHeader
方式。POST /*********POST 方式:如果你要傳 Form 資料給後端,資料格式如同 URL***********/
jsAjax.open("POST", "test.php"); //設定方法與傳送位置,布林值預設為 true(非同步)
jsAjax.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); //告知採用 Form-Data 為 POST 內容
jsAjax.send("form=data&number=1"); //URL 編碼方式塞入資料test.php echo $_SERVER["REQUEST_METHOD"]; //POST
print_r($_POST);
/*
Array(
[form] => data
[number] => 1
)
*/
//XMLHttpRequest 傳來的 POST 內容是 Form-Data
$data = file_get_contents('php://input',1);
echo $data; // form=data&number=1 - POST 方式(以 Request Payload 傳送):使用 JSON 方式傳送而不是傳統的 Form 表單內容。
POST /*********POST 方式:如果你要傳 JSON 或文串給後端,後端需要以 Request Payload 方式處理***********/
jsAjax.open("POST", "test.php"); //設定方法與傳送位置,布林值預設為 true(非同步)
jsAjax.setRequestHeader('Content-type', 'application/json'); //預設值可不寫
jsAjax.send(JSON.stringify({ form: 'data', number: 1 })); //不要直接丟 JSON 給後端會看不懂,需轉成 string。
/***************************************也可傳送字串類型包含 HTML
jsAjax.send(document);
jsAjax.send('string');
*********************************************/
console.log(jsAjax.readyState); // 1 => 同上參數原因:需要一些時間所以無法立刻用 readyState 看出變化PHP(後端) echo $_SERVER["REQUEST_METHOD"]; //POST
//XMLHttpRequest 傳來的 POST 內容是 Request Payload
$data = file_get_contents('php://input',1);
echo $data; // {"form":"data","number":1}
print_r(json_decode($data));
/*
stdClass Object(
[form] => data
[number] => 1
)
*/
- 取得資料來自 responseText
這裡可以分成兩種做法,分別是可透過事件 onreadystatechange(狀態變化時觸發)或 onload(作業結束)來完成回應之後續作業。- onreadystatechange
Method1:onreadystatechange jsAjax.onreadystatechange = () => { //一個 EventHandler(事件處理器)函式,會於 readyState 屬性之狀態改變時被呼叫。
console.log(jsAjax.readyState); // 2 (偵測到用 send) -> 3 (資料 loading 中) -> 4 (你撈到資料了,數據已經接收到)
if (jsAjax.readyState == 4) { //資料已收到
if (jsAjax.status == 200) { // HTTP 狀態碼:200 =>資料正確回傳,404 => 資料讀取錯誤
const dataType = jsAjax.getResponseHeader("Content-Type"); // 取得回應的資料類型 (String)
console.log(dataType);
/*
application/json; charset=UTF-8 => JSON
text/html; charset=UTF-8 => HTML or TEXT
*/
if (dataType.indexOf("json") == 0) console.log(JSON.parse(jsAjax.responseText)); //如果是 JSON 格式先轉換再輸出
if (dataType.indexOf("text") == 0) console.log(jsAjax.responseText); //如果是 TEXT 不處理直接輸出
} else console.log("資料錯誤");
}
} - onload
jsAjax.onload = () => {
console.log(jsAjax.status, jsAjax.readyState, "method2"); // 200 4 "method2"
/******do something********/
}
- onreadystatechange
jQuery 的 Ajax
jQuery 是第三方的 JavaScript 函式庫,裡面提供了 Ajax 的完整寫法與多種快速應用。
介紹 $.ajax
jQuery 的 ajax() 是以 jQuery 自己的 Ajax 技術核心來使用,相對來說對於 jQuery 其他的 Ajax 語法比較複雜但比起原生 JavaScript 來說仍然設定上比較快。主體為$.ajax(),其參數設定都是直接用 JSON 格式來設定屬性方法(除了 URL 與 TYPE,其他都非必要):
- $.ajax([settings])
核心物件,所有行為事件與回應都已參數傳遞進行指定。 - type:
HTTP 請求的方式 GET 或 POST。預設為 GET 可省略不寫 - dataType:
取回的資料該用什麼型態來讀取。輸入 xml, json, script, text, html 為選項值。可不指定由 jQuery 自動判斷。 - url:
目標的 URL 網址。 - data:
傳送給伺服器端的資料,通常以 JSON 方式傳送。 - scucess:
請求成功時所需執行的函式,且會結果以傳遞變數回傳。 - error:
請求失敗時所需執行的函式,並將整個請求之 XHR(XMLHttpRequest) 物件透過傳遞變數給你。 - complete:
完成事件後(不論成功或失敗)時所需執行的函式,並將整個請求之 XHR 物件透過傳遞變數給你。
beforeSend:
請求之前時所需執行的函式,也就是 Ajax 對 HTTP 請求之前的階段,並將整個請求之 XHR 物件透過傳遞變數給你。
$.ajax({ |
echo $_SERVER["REQUEST_METHOD"]; //GET |
介紹其他寫法
- jQuery.getJSON(url,data,success(data,status,xhr))
快速取得 JSON,必要為 url 與 success 函式而其他看需求,同時支援 JSONP 的用途。scucces 分別能提供三種傳遞變數,分別是取回資料、請求狀態、整個 XML(XMLHttpRequest) 之物件$.getJSON("test.json", { form: "data", number: 1 }, (result) => {
console.log("success:", result);
});
/***************************************same as
$.ajax({
type: "GET",
url: "test.json",
dataType:"json",
data: {
form: "data",
number: 1
},
success: (result) => {
console.log("success:", result);
}});
********************************************/ - $.get(url,data,success(response,status,xhr),dataType)
同以下寫法:$.ajax({
url: url,
data: data,
success: success(response,status,xhr),
dataType: dataType
}); - $.post(url,data,success(response,status,xhr),dataType)
同以下寫法:$.ajax({
type:'POST',
url: url,
data: data,
success: success(response,status,xhr),
dataType: dataType
}); - $.getScript(url,success(response,status,xhr))
能獲取來自另一個地方的腳本,通常讀取當下瀏覽器就會運行了所以不用指定 datatype,與<script src>
不同的是你可以決定何時加載。同等以下寫法:簡單範例如下:$.ajax({
type:'GET',
url: url,
success: success(response,status,xhr),
});$.getScript("test.js",(re)=>{
console.log("done",re);
});
/*
this is form test.js
done console.log("this is form test.js");
*/test.js console.log("this is form test.js");
- $(selector).load(url)
對 selector 進行 url 位置請求,並將取回之 HTML 替換其內容,範例如下:<ul></ul>
<script>
$("ul").load("list.html");
</script>
<!--
<ul>
<li>001</li>
<li>002</li>
<li>003</li>
<li>004</li>
<li>005</li>
</ul>
-->list.html <li>001</li>
<li>002</li>
<li>003</li>
<li>004</li>
<li>005</li>
jqXHR 與 Deferred 遞延物件
自 jQuery 版本 1.5 開始,所有的 Ajax 應用都會回傳 XMLHTTPRequest 物件的集合,並使用 Promise 建構函式完成窗口回應。你可以從 jqXML 物件找到 done(成功後執行)、fail(失敗後執行)、always(完成後執行,不論成功或失敗)。
另外 jQuery 還透過。Deferred() 方式串起可連接的導向鍊程序物件函式。可另添加 done()、fail()、always() 到你的 JQ Ajax 語法後面,包含所有的簡寫 Ajax 應用。
- done()、fail()、always()
提供成功結果、失敗結果、完成結果之回傳執行,舉例來說:$.ajax({
type: "GET",
url: "test.php",
dataType: "text",
data: {
form: "data",
number: 1
}
}).done((result) => {
console.log("done:", result);
/*done: GETArray(
[form] => data
[number] => 1
)*/
}).fail((result) => {
console.log("fail:", result); //失敗時,可看到 XHR 物件且 result.status:404
}).always((result) => {
console.log("always:", result); //成功時,可看到回傳內容;失敗時顯示 XHR 物件,可看到 result.Stateready:4
});
/********************* same as
$.ajax({
type: "GET",
url: "test.php",
dataType: "string",
data: {
form: "data",
number: 1
},
success: (result) => {
console.log("success:", result);
},
error: (result) => {
console.log("error:", result);
},
complete: (result) => {
console.log("complete:", result);
},
beforeSend: (result) => {
console.log("beforeSend:", result);
}
});
*/test.php echo $_SERVER["REQUEST_METHOD"]; //GET
print_r($_GET); - then(doneCallbacks,failCallbacks)
then 也是 Promise 下的產物,能提供同步延遲後的成功與失敗行為,簡單來說可以一筆取代 done() 與 fail() 的兩筆宣告。$.ajax({
type: "GET",
url: "test.php",
dataType: "text",
data: {
form: "data",
number: 1
}
}).then(
(result) => { //same as done()
console.log("then-done:", result);
},
(result) => { // same as fail()
console.log("fail:", result);
}); - when(Ajax,Ajax)
提供解決多個非同步問題下的回呼執行處理,隨著多個 Deferred 物件提供給 when() 執行,由 when 來收集這些返回的集合體再從 when 本身的 Deferred 物件來執行本身 then 或其他方法 (done,fail,always) 來執行所有結果。
- 如果 when() 的參數安插一個 Ajax 語法會回傳內容、狀態、XHR 物件,你可以接上 then() 來執行。
$.when(
$.ajax({
url: "text1.txt" // string "HELLO" in File
})
).then((data, textState, xhr) => {
console.log(data, textState, xhr);//HELLO scucess XHR Object
}); - 如果 when() 的參數安插多個 Ajax 語法會回傳內容、狀態、XHR 物件,你可以接上 then() 來執行。
$.when(
$.ajax({
url: "text1.txt"
}),
$.ajax({
url: "text2.txt"
})
).then(
(re1, re2) => { //done
console.log(re1, re2); // 能獲得兩組偽陣列格式 [responseText,state,XHR Object]
console.log(re1[0] + " " + re2[0]); //HELLO WORLD
},
(re1, re2) => { //
console.log(re1); // XHR Object
console.log(re2); // error
});hext1.text HELLO
hext2.text WORLD
- 如果 when() 的參數沒有指定,也會進行 Promise 處理返回,由於沒有請求只會 return 乾淨的 XHR 物件。如參數不是 Ajax 指令 (Deferred),則會直接 Callback 還你,相信你不會做這無聊的事。
$.when("AAA").then((e)=>{
console.log(e);//AAA
});
JavaScript 的 fetch API
從 ES6 開始提供了 fetch 如此方便的跨網路資源介面,主要是 XMLHttpRequest 舊技術的替代方案。同時越來越多人捨去 jQuery Ajax 技術直接使用原 JavaScript 來處理一切包含 Ajax 應用。提供 URL 位置給 fetch 接著透過 Promise 建構式來獲取 Response(不論成功或失敗都會取得回應),如果需要提供設定直接使用第二筆參數 init 物件(使用 Request 建構式)來調整。結論來說 fetch 只是一個官方設計過後的 API,且透過 Promise 與 Request 物件來達到 Ajax 應用(也可自行用 Promise 與 Request 物件 DIY 一個 Ajax 之 API,但何必這麼累)。
初次使用 Fetch (取得 JSON)
試著看看以下簡單範例步驟:
- 使用 fetch 並指定 url 位址,如果需要可以對第二參數(稱呼 init 物件)使用 JSON 格式編寫(這裡故意寫 GET ,預設 GET 可不寫)。
- 當透過 then() 來後續處理,fetch 最後回傳一個 Response 物件給你 (HTTP 回應資訊),這不是真正的內容結果。
- 要找到真正的內容,必需從 Response 裡面的 Body 物件(為 Body mixin 也是 Promise 與 Request 的物件 API,為申明內容該以什麼方式處理輸出),使用 Body 的 json() 或 text() 來處理內容回傳。
- 直接使用 Response.json() 方法讓我們透過 Body API 來回應出 JSON 字串,因此我們要再用一次 then() 來後續處理結果。
- 在那之前先 return response.json() 出來給下一個 then()。
- 第二個 then 將接受到 JSON 資料,才算真正取得內容。
fetch('test.json', { |
Fetch 可用設定
使用 Fetch 時可提供第二筆參數,這裡被稱呼為 init 物件且以 JSON 格式進行編寫提供 Request 使用(你可以在 MDN 找到 Request 的說明手冊),提供參數如下 (*代表預設值):
- method:
使用方法,如 *GET, POST, PUT, DELETE - headers:
表頭類型可以是 Headers 物件或 ByteString 值。舉例寫法:headers: {
'user-agent': 'Mozilla/4.0 MDN Example',
'content-type': 'application/json'
}
//////or
headers: new Headers({
'Content-Type': 'application/json'
}) - body:
BODY 指定的資料可以是 Blob, BufferSource, FormData, URLSearchParams, USVString。不允許對 GET 或 HEAD 方法使用。 - mode:
模式採用 cors(跨網), no-cors(非跨網), *same-origin(同源政策,限定同網域下)。 - credentials:
request 認證應用,表示用戶代理在跨網請求下能否發送 Cookie,可以是 *omit(不發送 Cookie), same-origin(同網域下才使用), include(可發送 Cookie)。 - cache:
請求的暫存模式,可以是 *default, no-store, reload, no-cache, force-cache, only-if-cached。 - redirect:
處理重新導向方式,可以是 *follow(自動重定向), error(如果產生重定向將自動終止並且拋出一個錯誤), manual(手動處理重定向)。 - referrer:
參照位址來源,表示從哪裡到目前的網頁。可以是 no-referrer, *client, URL。 - referrerPolicy:
指定 HTTP 表頭 referer 值。可以是 no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, unsafe-url。 - integrity:
资源完整性 (SRI) 的值,例如integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
,也很常在 CDN 引用時看到。
使用範例
- POST Request:(by FormData)
let data = new FormData(); // 需使用 FormData 建構式來做資料,由 body 去偵測採用 FormData 傳送
data.append("form","data");
data.append("number",1);
/*******反之有個情況整個 FORM 表單要提交,可以直接指定整個節點一起為 Data 送過去,FormData 建構式會聰明算進去
let data = new FormData(document.getElementById('myform'));
*******************************/
fetch("test.php", {
method: "POST",
body: data
})
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.log(error));
/********console.log********
POSTArray(
[form] => data
[number] => 1
)**************************/test.php echo $_SERVER["REQUEST_METHOD"]; //GET
print_r($_POST); - POST Request:(by Request Payload)
fetch("test.php", {
method: "POST",
body: JSON.stringify({ //須 JSON 物件轉成字串,後端才能判讀
form: "data",
number: 1
})
})
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.log(error));
/********console.log********
POSTRequest Payload:{"form":"data","number":1}
)**************************/test.php echo $_SERVER["REQUEST_METHOD"]; //GET
//XMLHttpRequest 傳來的 POST 內容是 Request Payload
$data = file_get_contents('php://input',1);
echo "Request Payload:".$data; // {"form":"data","number":1} - 確認成功與失敗
遇到 CORS 或 server 設定錯誤導致 network error 時,promise 會 reject 並附上 TypeError 的回應,但在權限或類似問題導致 404 的常見狀況下,卻不會導致 network error。因此是否成功的正確方式,應包含檢查 promise resolved, 以及檢查 Response.ok 的屬性是否為 true。fetch('404.json') //此 URL 故意不存在情況下
.then(response => {
console.log(response);
if(response.ok) return response.json();
throw new Error(response.status); //建立 error 物件提供給 catch() 取得,並中斷後續所有動作
console.log("這行不會被執行到");
})
.then(json => console.log(json))
.catch(error => console.log("發生錯誤代碼為"+error.message));//取出 error 物件的資訊顯示出來 - 多筆 Request 同時進行 Fetch
可透過 Promise.all() 方進行等待作業,最後一起 then() 完成動作。Promise.all([
fetch("textr1.txt").then(re => re.text()), // 'HELLO' string in file
fetch("text2.txt").then(re => re.text()) // 'WORLD' string in file
]).then(([r1, r2]) => {
console.log(r1, r2); //HELLO WORLD
});
停止取得
近期瀏覽器可以使用 AbortController API 來完成停止需求,等同於 xhr.abort() 行為或 xhr.onabort() 事件。
const controller = new AbortController(); |
axios 框架的 Ajax
fetch 尚未普及的年代的產物(目前也是很多人使用且為了脫離 jQuery 習慣,你也會在 vue 上面看到 axios 的蹤跡),類似 jQuery 需要額外套入的框架包。使用方式與 fetch 差不多,Axios 也依賴 JavaScript 的 promise 來完成 Ajax 應用。
你需要先安裝匯入 axios 使用,這裡採用 cdn 來處理。
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.0/axios.min.js"></script> |
初次使用 Axios API(取得 JSON)
類似 jQuery 的 Ajax 方法與 fetch 的 promiise,其語法為 axios(config)。除了完整寫法 axios API,也有其他簡寫方式,完整寫法如下:
axios({ |
axios API 的 config 可用設定
使用 axiosAPI 完整寫法,必需提供 config 物件內容,除了 URL 是必要的其他參數都可以因需求加入(未特別標示則為 string 值),使用參數非常的多可以分為幾大類:
一般參數
- url:
目標的 URL 網址,必填。 - method:
HTTP 請求的方式 GET 或 POST。預設為 GET 可省略不寫 - headers:
表頭類型可以是 Headers 物件或 ByteString 值。舉例寫法:headers: {
'user-agent': 'Mozilla/4.0 MDN Example',
'content-type': 'application/json'
}
//////or
headers: new Headers({
'Content-Type': 'application/json'
}) - baseURL:
目標的網域名稱,會自動添加在 url 前面。如果 url 已經是絕對路徑可省略。 - data:object
傳送的資料,只適合 PUT, POST, PATCH 類型使用。 - params:object
轉換資料為 URL 參數,適合 GET 變數的傳遞或類型使用。若你直接 URL 上寫好可省略。 - maxContentLength:number
限制內容傳送上限 - timeout:number
請求時間超過時間(毫秒)時,將終止此請求。 - responseType:
回傳的資料格式為何,可以是 arraybuffer, document, json’(預設), text, stream, blob - responseEncoding:
回應的資料其編碼模式為何,預設為 utf8
promise 前後處理
- onUploadProgress:(function), onDownloadProgress:(function)
在上下傳時可執行的函式,並可提供 ProgressEvent 物件作為傳遞參數onUploadProgress: function (progressEvt) { console.log(progressEvt) },
onDownloadProgress: function (progressEvt) { console.log(progressEvt) } - adapter:function()
不使用預設的 promise 處理,使用自定義的 promise 之函式作業 - transformRequest:[function(data){return}], transformResponse:[function(data){return}]
- Request 前的資料可以先處理,你可以提交之前對 data 做調整。此功能只對 PUT, POST, PATCH 有效。
- Response 前的資料可以先做處理。你可以提交之前對 data 做調整。此功能只對 PUT, POST, PATCH 有效。
axios({
method: 'post',
url: 'test.php',
transformRequest: [data => {
//這裡額外先將 JSON 物件轉成 FormData,使後端取得的會是 FormData 而不是透過 Request Payload 來拿
formData = new FormData();
for (const key in data) formData.append(key, data[key]);
return formData;
}],
transformResponse: [data => JSON.parse(data)],
//我們原取得的內容是 string,先轉成 JSON 才會往 then(),catch() 整合 Response
data: {
form: 'data',
number: 1
}
})
.then(response => console.log(response.data)) //取出來已經是 JSON 不用再處理
.catch(error => console.log(error));test.php echo json_encode($_POST);
網域驗證代理
- withCredentials:boolean
判斷是否為跨域存取 (CROS),預設為 false - auth:object
設定 Authorization 表頭資料,提供驗證使用。 - xsrfCookieName
用於 xsrf token 值的 cookie 名稱 - xsrfHeaderName
532xsrf token 值的 header 名稱 - validateStatus:function(status){return boolean}
判斷請求的網址狀態值為多少才會執行 Promise 的 reject 作業。預設操作為return 200 <= status && status < 300;
- proxy:object
定義代理伺服器,其中參數 auth 為 proxy 驗證帳密。proxy: {
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
} - cancelToken
用於清除 Token 的請求
Node.js
- maxRedirects:number
定義 node.js 需遵守的重定向 follow 次數 - httpAgent, httpsAgent
定義 node.js 執行 http 或 https 時的自訂代理配置
介紹所有 axios 速寫法
- axios.get(url, config)
axios.get("test.php", {
params: {
form: 'data',
number: 1
}
})
.then(response => console.log(response.data)) //取出來已經是 JSON(預設)不用再處理
.catch(error => console.log(error));test.php echo json_encode($_GET);
- axios.post(url, data, config)
沒有特別處理時,傳的內容會是 PayloadRequest Payload axios.post("test.php", {
form: 'data',
number: 1
})
.then(response => console.log(response.data)) //取出來已經是 JSON(預設)不用再處理
.catch(error => console.log(error));
//console: {form: "data", number: "1"}如果要用 FormData 傳過來,就不要用 json 格式,而是用 formData 來建構物件。test.php //傳來的 POST 內容是 Request Payload
$data = file_get_contents('php://input', 1);
echo $data; //{"form":"data","number":1}let bodyFormData = new FormData();
bodyFormData.append("form", "data");
bodyFormData.append("number", 1);
axios.post("test.php", bodyFormData)
.then(response => console.log(response.data)) //取出來已經是 JSON 不用再處理
.catch(error => console.log(error));formData echo json_encode($_POST);
- axios.all()
多筆 Request 同時進行,並藉由 axios.spread() 取出所有的 response。axios.all([
axios.get("text1.txt"), //string HELLO in file
axios.get("text2.txt") //string WORLD in file
]).then(
axios.spread(
(response1, response2) => {
console.log(response1.data, response2.data); //HELLO WORLD
}
)
).catch(error => console.log(error));
這裡還有其他的方式不再示範:
- axios(config):完整的寫法已示範過
- axios.request(config):與上相同功能
- axios.head(url, config):功能與 GET 相同但無 response body,也就是單向傳出不拿東西回來。
- axios.delete(url, config):DELETE 請求類型
- axios.put(url, data, config):PUT 請求類型
- axios.patch(url, data, config):PATCH 請求類型
- axios.options(url, config) // 檢查用,預先發送的請求是否安全。確認該地址採用的協定、要求的表頭等資訊。
- axios.create(config) //自定義設定 axios
axios.get("test.json").then(response => { |
認識 Promise
promise 從 ES6 的新語法,主要是解決程式執行順序上發生不同步的問題。也就是程式繼續往下跑不會等待某行的結果,這是為了工作效率而預定作業方式。例如可由 settimeout 來實現此問題:
let str = "AA"; |
因此許多 Ajax 應用方式因為需要從別處拿資料回來才能繼續執行,就可以選擇是否要求 async(非同步)為關閉。舉例 juqery 的舊方式 (1.5 版本開始也已改 promise 方法,像是 then 應用):
let data=null; |
這只是 jQuery Ajax 的非同步示範,如果以一般程式來聊要如何實現?因此 Promise 建構式就是來解決有需要做處理等待的程式代碼。導致現在很多 AjaxAPI 或框架幾乎都是靠 Promise 來處理同步等待的需求。
Promise 基本原型
Promise 直白點就是對你許下承諾(相信我,我一定會把佐助帶回來的?!)不論這個結果成功還是失敗。以下是 Promise 工作週期說明:
大致上 Promise 有三個階段:
- 擱置(pending):初始狀態。
- 實現(fulfilled):表示操作成功地完成。透過。then(onFullfillment) 來處理資料並回傳後,回到 pending 狀態。
- 拒絕(rejected):表示操作失敗了。透過。then(onRejection) 做失敗的應對並回傳,回到 pending 狀態。
使用 Promise 建構式時,需要提供 resolve 與 reject 兩個函式參數,分別在 fulfill 及 reject 階段作為 resolve 與 reject 函式的傳遞 status,使得。then() 與。catch() 能收到資料做後續作業。語法結構如下:
const myPromise = new Promise((resolve, reject) => { |
舉個例子:我們需要等到 Promise 工作完成後,根據某條件下成功或失敗決定要往 then 還是 catch 做之後的處理。整體作業是同步的,then 跟 catch 一定會等 Promise 作業結束才會觸發。
const mytime = new Promise((resolve, reject) => { |
回到工作階段說明圖,then() 透過 return 繼續緊接 then() 或 catch(),繼續往下一個階段但已經跟 Promise 無關,只是單純的往後面跑。下面範例不加入失敗考量,直接都是成功的連續 then 執行。
const sayABC = new Promise((resolve, reject) => { |
由於 then 可以 return,因此我們可以把一個 promise.then() 塞到另一個 promise 內形成連鎖的請求同步。
const say123 = new Promise((res, rej) => { |
如果是想印出 AB12,你應該在 AB 的 then() 才去建構出顯示 12 的 Promise。形成完整的同步處理並善用 then(return) 應用。
new Promise((resolve, reject) => { |
Promise 多項目方法
- Promise.all()
提供需要多個 promise 一起運作,同時最後一起跑到 then() 作業。提供參數為陣列寫法(可以是 Promise 物件也可以是一般變數內容。返回的資料也是陣列結構。注意的是如果只要有 Promise 是 reject 狀態,會導致 promise.all().then() 不會運作。Promise.all([
new Promise(resolve=>setTimeout(resolve("A"),1000)),
new Promise(resolve=>setTimeout(resolve("B"),1000)),
new Promise(resolve=>setTimeout(resolve(1),1000)),
2
]).then(data=>console.log(data)); //(4) ["A", "B", 1, 2]Promise.all([
new Promise(resolve => setTimeout(resolve("A"), 1000)),
new Promise(resolve => setTimeout(resolve("B"), 1000)),
new Promise((resolve, reject) => setTimeout(reject("fail"), 1000)),
2
]).then(data => console.log(data)) //this not work
.catch(err => console.log(err)); // console is fail - Promise.allSettled()
Promise.all() 的完整版,回傳的不會是內容而是整個 reponse 資訊,這樣不會因為某個 promise 失敗而導致停擺。你可以自行去取得各自的內容與應對。Promise.allSettled([
new Promise(resolve => setTimeout(resolve("A"), 1000)),
new Promise(resolve => setTimeout(resolve("B"), 1000)),
new Promise((resolve, reject) => setTimeout(reject("fail"), 1000)),
2
]).then(data => console.log(data)) //
/*
(4) [{…}, {…}, {…}, {…}]
0: {status: "fulfilled", value: "A"}
1: {status: "fulfilled", value: "B"}
2: {status: "rejected", reason: "fail"}
3: {status: "fulfilled", value: 2}
length: 4
*/ - Promise.race()
與 Promise.all 雷同,但只會取得第一個最快成功的 promise 就結束,但要注意只要一個失敗就會整個停擺。Promise.race([
new Promise(resolve => setTimeout(resolve, 3000, "A")),
new Promise(resolve => setTimeout(resolve, 1000, "B")),
]).then(data => console.log(data)) // "B"
async 與 awit
一種讓 promise 更好寫的方法。主要重點是兩個關鍵字宣告,async 放在某個函式前面代表這個函式等於 promise 且 return 出來的必定是成功的 resolved。
async function fn(){ |
另外的關鍵字 await,他必須是放在 async 裡面。主要是當 async 函式內的 promise 執行 resolve 才會觸發。也就拿來替代 then() 的寫法。
async function fn() { |
然而 async 內要發生 reject 事件指定 throw 即可。他會將資料傳給 catch 內。且會中斷整個函式執行。
const fn = async () => { |
舉例來說,使用 fetch() 函式本身會有自己 reject 事件,因此我們可以用 try-catch 來做一個語法表示。
const fn = async () => { |
如果不使用 try-catch 來做外框,那麼 catch 是落在 fn 本體身上。
const fn = async () => { |
總結來說:
- 使用 async/await 時,就不太會去使用 then()
- 常態性使用 try-catch,撰寫上的流程會比較直白些。
- await 必需要在 async 內,才能有作用。
- 要等待某一個結果請使用 await 放在函式或建構式前面,程式會等他完成才往下跑。
- 前綴有 async 的函式能代表他將會回傳 promise 物件需要等待。
- async/await 可以取代 promise.then/catch,但事實上他也是 promise 的語法。
API 串接
應用程式介面(Application Programming Interface)簡稱為 API 是個通俗的名稱,拿來解釋不同的軟體之間的溝通橋樑。透過請求 Request 與 Response 回應來完成資料的交換指令。在網頁上主要是前端與伺服器的溝通方式。透過 API 的指定網址與 Request 之 HTML 協定來進行溝通。
全名 Hypertext Transfer Protocol(超文本傳輸協定),每次進行網頁發送 request 請求時,伺服器端會檢查網址存在後,回發送 response 給你。這就是 HTTP 協定。如果網址不存在則會發送 404 的錯誤畫面給你(或其他因素提供對應的 HTTP response code 狀態碼)。
狀態碼大致可以分為五大類:
- 資訊回應 (Informational responses, 100–199),
- 成功回應 (Successful responses, 200–299),
- 重定向 (Redirects, 300–399),
- 用戶端錯誤 (Client errors, 400–499),
- 伺服器端錯誤 (Server errors, 500–599).
更多細節請查看 RFC 7231 規範
例如用 PHP 來宣告本頁的 response statue code 為 404,即使有畫面資料,透過 Ajax 抓取會得到 404 結果:
echo $_SERVER["REQUEST_METHOD"]; |
然而要進行請求時,根據 HTML 協定的 method 歸類可以分為 GET(查詢) / POST(新增) / PUT(修改) / DELETE(刪除)為主,也就是對應了 CRUD 行為請求之觀念。在沒有聊到 RESTful 觀念之前,API 串接方式沒有一定方式,主要是看伺服器要如何接手你的 request(包含判別 GET/POST/PUT/DELETE)來進行怎樣的動作自己規定。
RESTful
真正的名字應該稱呼為 REST(Representational State Transfer) 表現層狀態轉換。是一種風格做法而不是一個標準。而 RESTful 只是一個形容詞(就像 beautiy->beautiful),只要符合這種做法都可以稱呼這個 API 有遵循 REST 機制做法,是屬於 Restful API 的設計。
符合 REST 設計風格的 Web API 稱為 RESTful API。它從以下三個方面資源進行定義:
- 直觀簡短的資源 URI 位址,例如:
http://demo.com/api
。 - 傳輸的資源:Web 服務接受與返回的網際網路媒體類型,比如:JSON, XML, YAML 等。
- 對資源的操作:Web 服務在該資源上所支援的一系列請求方法(例如:POST, GET, PUT, DELETE…)共 8 種。
與傳統 API 來比較,可能普通 API 的請求 URI 位置舉例如下:
- 取得:
http://demo.com/api/get/
- 新增:
http://demo.com/api/post/
- 更新:
http://demo.com/api/update/3
- 刪除:
http://demo.com/api/delete/3
至於 RESTful API 的規則舉例如下:
- 取得:
http://demo.com/api/
+ HTTP Method GET - 新增:
http://demo.com/api/
+ HTTP Method POST - 更新:
http://demo.com/api/3
+ HTTP Method PUT - 刪除:
http://demo.com/api/3
+ HTTP Method DELETE
也就是 URI 只有一個,有任何 Verbs 資源動作都寫在 Method 裡面。而 URI 這裡指的是(Uniform Resource Identifier)統一資源識別符號,也就是某檔案路徑下的資源所在之請求位置。另外說明 URL 為 Uniform Resource Locatin 指的是一個檔案路徑。所以 URL 的後面多一個動作行為就能拿來視為 URI。規劃極簡的 URI 也是 REST 要求的重點之一,這需要透過路由或 apache 的 htaccess 重寫規則來轉換。
規則可以在細分一點,舉例為文章清單應用:
GET http://demo.com/page/
瀏覽全部文章GET http://demo.com/page/1
瀏覽單筆文章且 ID 為 1POST http://demo.com/page/
新增一筆文章PATCH http://demo.com/page/1
修改單筆文章且 ID 為 1PUT http://demo.com/page/1
修改單筆文章且 ID 為 1,若尚未建立則為新增DELETE http://demo.com/page/
刪除單筆文章 ID 為 1
RESTful 不是一定的規定,可以混搭自己需要的路由操作目標。譬如可以DELETE http://demo.com/page/login
做登入驗證請求。RESTful 只是將 CRUD 的方式做一下習慣建議。在 Ajax 應用當中都有提供所有有 Method 方式,而後端需要判別時可透過偵測 requst method 方式為何,部分處理若不會有回應內容時,可 response 下達 ststus code(例如 202 為 no content)。舉例 PHP 來說:
//PHP 能直接對$_GET 與$_POST 做存取,其他則需要透過 request method 查找 |
OpenData API
國內提供非常多元的免費 API 串接,也就是 OpenData 讓你直接用。大也不是啥都能順利套用以及保持更新。所以在找到任何 API 來源時推薦先驗證測試使用。以下推三種方法來做 API 測試:
測試方式
- Postman REST Client
算是很多人使用的方式,免費下載 提供使用,網路資源很多不多介紹。 - VSCode Plugin-REST Client
REST Client 直接在 VSCode 就能測試,你需要先建立一個檔名。http
並貼上 Request 之 URL 會自動生成提示 Send Request 之字眼按下送出即可。會產生 Response 資訊與內容方便文字複製。若需其他 REST Method 可在網址前綴補上關鍵字如下:demo.http GET https://tw.rter.info/capi.php
CORS 問題與解決
在使用 API 進行溝通時尤其對外部 OpenData API 請求時,有機會發生錯誤訊息。
has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
這是用戶的瀏覽器在執行時根據 W3C 規範而阻止你做了這樣的事情,當 JS 指令需要跟某伺服器請求時,根據同源政策 (Same Origin Policy) 下只有同 domain 的資源才可相互存取防範駭客攻擊。如果是自家後端的伺服器要開放允許很簡單,舉例 PHP 來說只需要設定以下開頭宣告即可:
header('Access-Control-Allow-Origin:*'); |
伺服器開放 CORS
因此當你碰到 API 請求失敗時,很大因素是對方工程師沒有特別開放給任何人連線。除非你有能力要求對方全面開放或額外開放指定你的網頁 Domain 能請求:
|
透過 PHP 存取
同上話題,你應該沒有那能力去要求提供 OpenData 服務商開放,所以大部分的人都會用爬蟲方式來取得。可幸的是只有 JS 的 Ajax 會被阻擋,反而 PHP, python,Node.js 這些後端程式都可以正常存取別人的 API(Postman 也是)。簡單來說就是爬蟲方法,以下舉例 PHP 兩種 API 存取方式。
- file_get_contents
適合存取資料量少且更新不頻繁的對象,整體上效能低資源吃重。但相對語法上簡單就能完成。file_get_content $content = file_get_contents('https://tw.rter.info/capi.php');
$data1 = json_decode($content);
print_r($data1); - curl
因有提供暫存功能,適合資料量大且整體運作效能高,更包含多種參數可特別使用。如果允許請盡量使用 CURL 方式存取。curl function load_contents($url)
{
$ch = curl_init(); // 初始化 CURL
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //回傳移除,預設 false: 自動輸出至畫面。true 則不輸出(可繼續編輯處理變數)
curl_setopt($ch, CURLOPT_URL, $url); //目標網址
/****************************************************************非必要的設定
curl_setopt($ch, CURLOPT_HEADER, false); // 是否需取得 HEAD 資訊
預設 TRUE 驗證伺服器憑證,拜訪 https 若未做任何 SSL 設定會出錯。
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
發出端的用戶代理,提供軟體類型、版本號、作業系統、開發者資訊。參考 https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Headers/User-Agent
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
*******************************************************************/
$data = curl_exec($ch); // 執行 curl
curl_close($ch); // 關閉 curl
return $data;
}
$content = load_contents('https://tw.rter.info/capi.php');
$data2 = json_decode($content);
print_r($data2);
若你有能力自行建立伺服器當作跳板,用自家的後端去存取別人伺服器的資料回來,再讓同網域下(或特別開放)的前端 JS 向此處存取。
- 透過第三方線上跳板 cors-anywhere
若沒有能力自己架設伺服器,cors-anywhere 提供專門解決 CORS 的免費伺服器跳板,根據使用方式說明只需設定以下代碼即可。
const cors = 'https://cors-anywhere.herokuapp.com/'; // use cors-anywhere to fetch api data |
免費 OpenData 平台
參考文獻
Fetch API - Web APIs | MDN
WorkerOrGlobalScope.fetch() - Web API 接口参考 | MDN
使用 Fetch - Web API 接口参考 | MDN
跨來源資源共用(CORS) - HTTP | MDN
Request - Web API 接口参考 | MDN
使用说明 · Axios 中文说明 · 看云
axios 基本使用 & Config - iT 邦幫忙:: 一起幫忙解決難題,拯救 IT 人的一天
從 Promise 開始的 JavaScript 異步生活
stackoverflow- chaining multiple XHR calls
Promise,async/await
MDN-Promise
XMLHttpRequest vs the Fetch API for Ajax - SitePoint