編者按:除創宇前端與做者博客外,本文還在語雀發佈。javascript
編者還要按:做者也在掘金哦,歡迎關注:@GoDotDotDothtml
本篇主要講述 Fetch 的一些基本知識點以及咱們在生產開發中怎麼去使用。爲了可以更好的瞭解 Fetch,咱們但願你對如下知識點有所瞭解,若是有相關的開發經驗,那是最好不過的了。前端
本文中對有些關鍵詞提供了相應的連接,若是你對該關鍵詞不夠了解或想要了解更多,你能夠經過點擊它充實本身。文中有些知識點在 MDN Fetch 上已經寫的很詳細,所以有略過,但願同窗們在閱讀本文章時可以同時對照閱讀。java
本文行文思路首先從規範入手,目的是讓你們瞭解的更透徹,達到知其然知其因此然。node
爲了更好的掌握 Fetch,文章中還提供了一些示例代碼供你們學習使用。在使用該示例代碼前,咱們但願你對 node.js 有一些瞭解,若是沒有的話,你能夠根據示例中的友情提示完成你的此次學習體驗。c++
讀完本篇文章後你將瞭解到如下內容:git
但願你經過讀完本篇文章後,對 Fetch 有一個基本的瞭解。github
Fetch 是一種新的用於獲取資源的技術,它被用來代替咱們已經吐槽了好久的技術(XHR)。web
Fetch 使用起來很簡單,它返回的是一個 Promise,即便你沒有 XHR 的開發經驗也能快速上手。說了那麼多,咱們仍是先睹爲快吧,讓咱們快快下面的示例代碼。json
fetch('https://github.com/frontend9/fe9-library', {
method: 'get'
}).then(function(response) {
}).catch(function(err) {
// Error
});
複製代碼
是否是簡單的不能再簡單了?好,既然咱們 Fetch 有了簡單的認識以後,那咱們再來了解下 Fetch 的基本概念。
在 Fetch 中有四個基本概念,他們分別是 Headers、Request 、Response 和 Body。爲了更好的理解 Fetch,咱們須要對這些概念作一個簡單的瞭解。
在一個完整的 HTTP 請求中,其實就已經包含了這四個概念。請求中有請求頭和請求體,響應中有響應頭和響應體。因此咱們有必要了解這些概念。
爲了實現頭部的靈活性,可以對頭部進行修改是一個很是重要的能力。Headers 屬於 HTTP 中首部的一份子,它是一個抽象的接口,利用它能夠對 HTTP 的請求頭和響應頭作出添加、修改和刪除的操做。
下面咱們先看一下它具備哪些接口:
typedef (sequence<sequence<ByteString>> or record<ByteString, ByteString>) HeadersInit;
[Constructor(optional HeadersInit init),
Exposed=(Window,Worker)]
interface Headers {
void append(ByteString name, ByteString value);
void delete(ByteString name);
ByteString? get(ByteString name);
boolean has(ByteString name);
void set(ByteString name, ByteString value);
iterable<ByteString, ByteString>;
};interface Headers {
void append(ByteString name, ByteString value);
void delete(ByteString name);
ByteString? get(ByteString name);
boolean has(ByteString name);
void set(ByteString name, ByteString value);
iterable<ByteString, ByteString>;
};
// 來自 https://fetch.spec.whatwg.org/#headers-class
複製代碼
規範中定義的接口咱們能夠對應着 MDN 進行查看,你能夠點擊這裏更直觀的看看看看它有哪些方法供咱們使用。
這裏咱們對 Headers
的構造參數作個解釋。首先參數類型爲 HeadersInit
,咱們再看下這個類型支持哪些類型的值。咱們從規範中能夠看到的定義是:
typedef (sequence<sequence<ByteString>> or record<ByteString, ByteString>) HeadersInit;
複製代碼
這裏咱們對應到 JavaScript
這門語言,意思就是說這個對象能夠是數組或者是鍵值對(即對象)。關於如何初始化這些參數,咱們能夠看下規範中定義的流程。
To fill a
Headers
object (headers) with a given object (object), run these steps:
這裏我須要對這個作個說明,後面對 fetch 的用法會涉及到一點以及咱們看 polyfill 都會有所幫助。
header
名,第二項是值。,最後直接經過 append
方法添加。append
方法添加。示例代碼地址:github.com/GoDotDotDot…
打開瀏覽器輸入:http://127.0.0.1:4000/headers
那麼咱們該如何使用它呢?首先咱們須要經過 new Headers()
來實例化一個 Headers 對象,該對象返回的是一個空的列表。在有了對象實例後,咱們就能夠經過接口來完成咱們想要的操做,咱們來一塊兒看看下面的示例:
function printHeaders(headers) {
let str = '';
for (let header of headers.entries()) {
str += ` <li>${header[0]}: ${header[1]}</li> `;
console.log(header[0] + ': ' + header[1]);
}
return `<ul> ${str} </ul>`;
}
const headers = new Headers();
// 咱們打印下看看是否返回的是一個空的列表
const before = printHeaders(headers); // 發現這裏沒有任何輸出
document.getElementById('headers-before').innerHTML = before;
// 咱們添加一個請求頭
headers.append('Content-Type', 'text/plain');
headers.append('Content-Type', 'text/html');
headers.set('Content-Type', ['a', 'b']);
const headers2 = new Headers({
'Content-Type': 'text/plain',
'X-Token': 'abcdefg',
});
const after = printHeaders(headers); // 輸出:content-type:
複製代碼
若是你以爲每次都要 append
麻煩的話,你也能夠經過在構造函數中傳入指定的頭部,例如:
const headers2 = new Headers({
'Content-Type': 'text/plain',
'X-Token': 'abcdefg'
});
printHeaders(headers2);
// 輸出:
// content-type: text/plain
// x-token: abcdefg
複製代碼
這裏我添加了一個自定義頭部 X-Token
,這在實際開發中很常見也頗有實際意義。可是切記在 CORS 中須要知足相關規範,不然會產生跨域錯誤。
你能夠經過append
、 delete
、set
、get
和has
方法修改請求頭。這裏對 set
和 append
方法作個特殊的說明:
set
: 若是對一個已經存在的頭部進行操做的話,會將新值替換掉舊值,舊值將不會存在。若是頭部不存在則直接添加這個新的頭部。
append
:若是已經存在該頭部,則直接將新值追加到後面,還會保留舊值。
爲了方便記憶,你只須要記住 set
會覆蓋,而 append
會追加。
Guard 是 Headers 的一個特性,他是一個守衛者。它影響着一些方法(像 append
、 set
、delete
)是否能夠改變 header 頭。
它能夠有如下取值:immutable
、request
、request-no-cors
、response
或 none
。
這裏你無需關心它,只是爲你讓你瞭解有這樣個東西在影響着咱們設置一些 Headers。你也沒法去操做它,這是代理的事情。舉個簡單的例子,咱們沒法在 Response Headers 中插入一個 Set-Cookie
。
若是你想要了解更過的細節,具體的規範請參考 concept-headers-guard 和 MDN Guard
TypeError
。Body 準確來講這裏只是 mixin,表明着請求體或響應體,具體由 Response
和 Request
來實現。
下面咱們來看看它具備哪些接口:
interface mixin Body {
readonly attribute ReadableStream? body;
readonly attribute boolean bodyUsed;
[NewObject] Promise<ArrayBuffer> arrayBuffer();
[NewObject] Promise<Blob> blob();
[NewObject] Promise<FormData> formData();
[NewObject] Promise<any> json();
[NewObject] Promise<USVString> text();
};
// 來自 https://fetch.spec.whatwg.org/#body
複製代碼
規範中定義的接口咱們能夠對應着 MDN 進行查看,你能夠點擊這裏更直觀的看看它有哪些屬性和方法供咱們使用。
這裏須要注意看這些方法返回的都是 Promise
,記住這在基於 fetch
進行接口請求中很重要。記住了這個,有利於咱們在後面的文章中理解 fetch
的用法。
範例將在 Response
中體現。
Request 表示一個請求類,須要經過實例化來生成一個請求對象。經過該對象能夠描述一個 HTTP 請求中的請求(通常含有請求頭和請求體)。既然是用來描述請求對象,那麼該請求對象應該具備修改請求頭(Headers)和請求體(Body)的方式。下面咱們先來看下規範中 Request 具備哪些接口:
typedef (Request or USVString) RequestInfo;
[Constructor(RequestInfo input, optional RequestInit init),
Exposed=(Window,Worker)]
interface Request {
readonly attribute ByteString method;
readonly attribute USVString url;
[SameObject] readonly attribute Headers headers;
readonly attribute RequestDestination destination;
readonly attribute USVString referrer;
readonly attribute ReferrerPolicy referrerPolicy;
readonly attribute RequestMode mode;
readonly attribute RequestCredentials credentials;
readonly attribute RequestCache cache;
readonly attribute RequestRedirect redirect;
readonly attribute DOMString integrity;
readonly attribute boolean keepalive;
readonly attribute boolean isReloadNavigation;
readonly attribute boolean isHistoryNavigation;
readonly attribute AbortSignal signal;
[NewObject] Request clone();
};
Request includes Body;
dictionary RequestInit {
ByteString method;
HeadersInit headers;
BodyInit? body;
USVString referrer;
ReferrerPolicy referrerPolicy;
RequestMode mode;
RequestCredentials credentials;
RequestCache cache;
RequestRedirect redirect;
DOMString integrity;
boolean keepalive;
AbortSignal? signal;
any window; // can only be set to null
};
enum RequestDestination { "", "audio", "audioworklet", "document", "embed", "font", "image", "manifest", "object", "paintworklet", "report", "script", "sharedworker", "style", "track", "video", "worker", "xslt" };
enum RequestMode { "navigate", "same-origin", "no-cors", "cors" };
enum RequestCredentials { "omit", "same-origin", "include" };
enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" };
enum RequestRedirect { "follow", "error", "manual" };
// 來自 https://fetch.spec.whatwg.org/#request-class
複製代碼
規範中定義的接口咱們能夠對應着 MDN 進行查看,你能夠點擊這裏更直觀的看看它有哪些屬性和方法供咱們使用,這裏不作一一解釋。
注意這裏的屬性都是隻讀的,規範中咱們能夠看到構造函數的第一個參數爲 Request
對象或字符串,咱們通常採起字符串,即須要訪問的資源地址( HTTP 接口地址)。第二個參數接收一個 RequestInit
可選對象,而這個對象是一個字典。在 javascript
中,咱們能夠理解爲一個對象({})。RequestInit
裏面咱們能夠配置初始屬性,告訴 Request
咱們這個請求的一些配置信息。
這裏咱們須要對如下幾個屬性特別注意下。
mode 是一個 RequestMode
枚舉類型,可取的值有 navigate
, same-origin
, no-cors
, cors
。它表示的是一個請求時否使用 CORS,仍是使用嚴格同源模式。當處於跨域狀況下,你應當設置爲 cors
。該值的默認值在使用 Request
初始化時,默認爲 cors
。當使用標記啓動的嵌入式資源,例如 <link>
、 <script>
標籤(未手動修改 crossorigin 屬性),默認爲 no-cors
。詳細信息請參考 whatwg 規範或 MDN 。
credentials 是一個 RequestCredentials
枚舉類型,可取的值有 omit
, same-origin
, include
。它表示的是請求是否在跨域狀況下發送 cookie
。看到這,若是對 XHR 瞭解的同窗應該很熟悉。這和 XHR 中的 withCredentials
很類似。可是 credentials
有三個可選值,它的默認值爲 same-origin
。當你須要跨域傳遞 cookie
憑證信息時,請設置它爲 include
。注意這裏有一個細節,當設置爲 include
時,請確保 Response Header
中 Access-Control-Allow-Origin
不能爲 *
,須要指定源(例如:http://127.0.0.1:4001),不然會你將會在控制檯看到以下錯誤信息。詳細信息請參考 whatwg 規範或 MDN 。
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
你可使用文章中提供的代碼中啓動 cors 示例代碼,而後在瀏覽器中輸入 http://127.0.0.1:4001/request,若是不出意外的話,你能夠在控制檯中看到上面的錯誤提示。
body 是一個 BodyInit 類型。它可取的值有 Blob
,BufferSource
, FormData
, URLSearchParams
, ReadableStream
, USVString
。細心的同窗不知道有沒有發現,咱們常見的 json
對象卻不在其中。所以,咱們若是須要傳遞 json
的話,須要調用 JSON.stringify
函數來幫助咱們轉換成字符串。
下面將給出一段示例代碼。
示例代碼地址:github.com/GoDotDotDot…
打開瀏覽器輸入:http://127.0.0.1:4000/request
// 客戶端
const headers = new Headers({
'X-Token': 'fe9',
});
const request = new Request('/api/request', {
method: 'GET',
headers,
});
console.log(request); // Request {method: "GET", url: "http://127.0.0.1:4000/api/request", headers: Headers, destination: "", referrer: "about:client", …}
console.log(request.method); // GET
console.log(request.mode); // cors
console.log(request.credentials); // same-origin
// 若是你想打印headers信息,能夠調用 printHeaders(request.headers)
複製代碼
這裏咱們先以 GET
簡單請求做爲示例,咱們傳遞了一個自定義的 Headers
,指定了請求方法 method
爲 GET
(默認爲 GET
)。在上面的接口規範中,咱們能夠經過 Request
對象拿到一些經常使用的屬性,好比 method
、url
、headers
、body
等等只讀屬性。
Response 和 Request 相似,表示的是一次請求返回的響應數據。下面咱們先看下規範中定義了哪些接口。
[Constructor(optional BodyInit? body = null, optional ResponseInit init), Exposed=(Window,Worker)]
interface Response {
[NewObject] static Response error();
[NewObject] static Response redirect(USVString url, optional unsigned short status = 302);
readonly attribute ResponseType type;
readonly attribute USVString url;
readonly attribute boolean redirected;
readonly attribute unsigned short status;
readonly attribute boolean ok;
readonly attribute ByteString statusText;
[SameObject] readonly attribute Headers headers;
readonly attribute Promise<Headers> trailer;
[NewObject] Response clone();
};
Response includes Body;
dictionary ResponseInit {
unsigned short status = 200;
ByteString statusText = "";
HeadersInit headers;
};
enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" };
// 來自 https://fetch.spec.whatwg.org/#response-class
複製代碼
規範中定義的接口咱們能夠對應着 MDN 進行查看,你能夠點擊這裏更直觀的看看它有哪些屬性和方法供咱們使用,這裏不作一一解釋。
其中 status
, headers
屬性最爲經常使用。經過 status
狀態碼咱們能夠判斷出服務端請求處理的結果,像 200
, 403
等等常見狀態碼。這裏舉個例子,當 status
爲 401
時,能夠在前端進行攔截跳轉到登陸頁面,這在現現在 SPA
(單頁面應用程序)中尤其常見。咱們也能夠利用 headers
來獲取一些服務端返回給前端的信息,好比 token
。
仔細看上面的接口的同窗能夠發現 Response includes Body;
這樣的標識。在前面咱們說過 Body
由 Request
和 Response
實現。因此 Body
具備的方法,在 Response
實例中均可以使用,而這也是很是重要的一部分,咱們經過 Body
提供的方法(這裏準確來講是由 Response
實現的)對服務端返回的數據進行處理。
下面咱們將經過一個示例來了解下簡單用法:
示例代碼位置:github.com/GoDotDotDot…
// 客戶端
const headers = new Headers({
'X-Token': 'fe9-token-from-frontend',
});
const request = new Request('/api/response', {
method: 'GET',
headers,
});
// 這裏咱們先發起一個請求試一試
fetch(request)
.then(response => {
const { status, headers } = response;
document.getElementById('status').innerHTML = `${status}`;
document.getElementById('headers').innerHTML = headersToString(headers);
return response.json();
})
.then(resData => {
const { status, data } = resData;
if (!status) {
window.alert('發生了一個錯誤!');
return;
}
document.getElementById('fetch').innerHTML = data;
});
複製代碼
這裏咱們先忽略 fetch
用法,後面的章節中會進行詳細介紹。咱們先關注第一個 then
方法回調裏面的東西。能夠看到返回了一個 response
對象,這個對象就是咱們的 Response
的實例。示例中拿了 status
和 headers
,爲了方便,這裏我將其放到 html 中。再看看該回調中最後一行,咱們調用了一個 response.json()
方法(這裏後端返的數據是一個 JSON
對象,爲了方便直接調用 json()
),該方法返回一個 Promise
,咱們將處理結果返給最後一個 then
回調,這樣就能夠得到最終處理事後的數據。
打開瀏覽器,輸入 http://127.0.0.1:4000/response,若是你的示例代運行正常,你將會看到如下頁面:
(查看 Response 返回的數據)
編者注:本文未完待續。
文 / GoDotDotDot
Less is More.
編 / 熒聲
做者其餘文章:
本文由創宇前端做者受權發佈,版權屬於做者,創宇前端出品。 歡迎註明出處轉載本文。文章連接:blog.godotdotdot.com/2018/12/28/…
想要訂閱更多來自知道創宇開發一線的分享,請搜索關注咱們的微信公衆號:創宇前端(KnownsecFED)。歡迎留言討論,咱們會盡量回復。
感謝您的閱讀。
新年快樂 :)