前端 fetch 通信


隨着前端異步的發展, XHR 這種耦合方式的書寫不利於前端異步的 Promise 回調. 而且,寫起來也是很復雜. fetch API 本來是在 SW(ServiceWorkers) 中提出的, 不過, 后面覺得好用, 就把他掛載到 window 對象下. 這樣, 在前端的正常通信中, 我們也可以直接調用. 但, fetch 畢竟比較新, 看一下他的兼容性.

fetch compatiable

在 PC 端上, 就 FF, Opera 和 Chrome 比較 fashion. mobile 的話, 基本上是不能用的. 當然, 前端一直是個擁抱變化的職業, 官方已經有一個現成的 polyfill 可以使用. 這樣的話, 就沒必要過多擔心.

每用到一個新的 feature, 我們首先得知道他能不能用. Modernizr 這個庫做的挺好的. 這里, 我們簡單的了解一下就 ok 了.

let isFetch = window.fetch?true:false; 

fetch 基本格式

可以說, fetch 就是 ajax + Promise. 使用的方式和 jquery 提供的 $.ajax() 差不多.

fetch('./api/some.json') .then( function(response) { if (response.status !== 200) { console.log(`返回的響應碼${response.status}`); return; } // 獲得后台實際返回的內容 response.json().then(function(data) { console.log(data); }); } ) .catch(function(err) { console.log('Fetch Error :-S', err); }); 

上面的 demo 很好的參數了, fetch 的幾個特點.

  • then()
  • catch()
  • json()

then 和 catch 是 promise 自帶的兩個方法, 我這里就不多說了. 我們來看一下,json 是干嘛的.

因為返回回來的數據不僅僅包括后台的返回的 Text 內容, 還有一些 Headers. 所以在, then 方法里面返回來的 res 實際上並不是我們在業務中想要的內容. 就和在 XHR 里返回數據是一個道理, 我們最終要的是 responseText 這個數據. 而 json() 方法實際做的事情,就是調用 JSON.parse() 處理數據, 並且返回一個新的 Promise. 看一下 polyfill 源碼就應該了解.

this.json = function() { return this.text().then(JSON.parse) } 

這里需要注意的是,fetch 中的 response/request 都是 stream 對象。

fetch 傳輸格式

上面的 demo 是一個 get 方法的請求, 當然, 除了 get , 還有其他的 HTTP Method, PUT, DELETE, POST, PATCH 等. 這里, 我們就說一個 POST, 其他方法的基本格式還是類似的.

fetch("http://www.example.org/submit.php", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "this is a post Msg" }).then(function(res) { if (res.ok) { // doSth } else if (res.status == 401) { // doSth } }); 

看起來 fetch 和 $.ajax 並沒有多大的區別...
but... fetch 里面的內容,真不少. 往底層看一看, fetch 實際上是 Request,Headers,Response 3個接口的整合. 不過, 這3個只能在 SW 里面使用. 這里當做原理,參數一下即可.

Headers 操作

Headers 的操作無非就是 CRUD, 這里我就不過多贅述,直接看代碼吧:

var content = "Hello World"; var reqHeaders = new Headers(); reqHeaders.append("Content-Type", "text/plain" reqHeaders.append("Content-Length", content.length.toString()); reqHeaders.append("X-Custom-Header", "自定義頭"); 

當然, 你也可以使用字面量的形式:

reqHeaders = new Headers({ "Content-Type": "text/plain", "Content-Length": content.length.toString(), "X-Custom-Header": "自定義頭", }); 

接下來就是, 頭部的內容的檢測相關.

console.log(reqHeaders.has("Content-Type")); // true console.log(reqHeaders.has("Set-Cookie")); // false reqHeaders.set("Content-Type", "text/html"); reqHeaders.append("X-Custom-Header", "新增自定義頭"); console.log(reqHeaders.get("Content-Length")); // 11 console.log(reqHeaders.getAll("X-Custom-Header")); // ["自定義頭", "新增自定義頭"] reqHeaders.delete("X-Custom-Header"); console.log(reqHeaders.getAll("X-Custom-Header")); // [] 

不過, 鑒於安全性的考慮, 有時候在多人協作或者子版塊管理時, 對於頭部的限制還是需要的. 這里, 我們可以通過 guard 屬性, 設置 Headers 的相關策略.
guard 通常可以取 "immutable", "request", "request-no-cors", "response", "none".
我們這里不探討全部, 僅僅看一下 request 這個選項.
當你設置了 request 之后, 如果你設置的 Header 涉及到 forbidden header name (這個是瀏覽器自動設置的), 那么該次操作是不會成功的.
forbidden header name 通常有.

  • Accept-Charset
  • Accept-Encoding
  • Access-Control-Request-Headers
  • Access-Control-Request-Method
  • Connection
  • Content-Length
  • Cookie
  • Cookie2
  • Date
  • DNT
  • Expect
  • Host
  • Keep-Alive
  • Origin
  • Referer
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade
  • Via

對比與 fetch, 我們沒有辦法去設置 Headers的 guard, 所以, 這只能在 SW 里使用.

Request 操作

Request 的基本用法和 fetch 差不多.

var uploadReq = new Request("/uploadImage", { method: "POST", headers: { "Content-Type": "image/png", }, body: "image data" }); fetch("/uploadImage", { method: "POST", headers: { "Content-Type": "image/png", }, body: "image data" }); 

那 fetch 有什么用呢?
關鍵的地方在於,fetch 實際上就是 request/reponse 的容器,request/response 相當於就是兩個元數據,fetch 只是實際進行的操作。所以,為了達到更高的復用性,我們可以 ajax 的請求,實例化為一個個具體的對象。

var getName = new Request(...,{//...}); var getGender = new Request(...,{//...}); // 發送請求 fetch(getName) .then((res)=>{}); fetch(getGender) .then((res)=>{}); 

在瀏覽器里, 一切請求都逃不過跨域和不跨域的問題. fetch 也是. 對於跨域的請求, 主要的影響還是體現在 Response 中, 這 fetch Request 這, 沒多大影響. 不過, 我們需要在 fetch 設置 mode 屬性, 來表示這是一個跨域的請求.

fetch('https://www.villainhr.com/cors-enabled/some.json', {mode: 'cors'}) .then(function(response) { return response.text(); }) 

常用的 mode 屬性值有:

  • same-origin: 表示只請求同域. 如果你在該 mode 下進行的是跨域的請求的話, 那么就會報錯.
  • no-cors: 正常的網絡請求, 主要應對於沒有后台沒有設置 Access-Control-Allow-Origin. 話句話說, 就是用來處理 script, image 等的請求的. 他是 mode 的默認值.
  • cors: 用來發送跨域的請求. 在發送請求時, 需要帶上.
  • cors-with-forced-preflight: 這是專門針對 xhr2 支持出來的 preflight,會事先多發一次請求給 server,檢查該次請求的合法性。

另外, 還有一個關於 cookie 的跨域內容. 在 XHR2 中,我們了解到, withCredentials 這個屬性就是用來設置在進行跨域操作時, 對不同域的 Server 是否發送本域的 cookie. 一般設置為 omit(不發送). 在 fetch 當中, 使用的是 credentials 屬性.
credentials 常用取值為:

  • omit: 發送請求時,不帶上 cookie. 默認值.
  • same-origin: 發送同域請求時,會帶上 cookie.
  • include: 只要發送請求,都會帶上 cookie.

所以, 如果你想發送 ajax 時, 帶上 cookie, 那么你就需要使用 same-origin, 如果想在跨域時也帶上 cookie, 那么就需要 include.

// 跨域請求 fetch('https://www.villainhr.com/cors-enabled/some.json', {mode: 'cors',credentials:'include'}) .then(function(response) { return response.text(); }) 

Response 操作

response 應該算和 fetch 最為接近的一個對象. Response 的實際其實就是 fetch 回調函數傳回的參數. Response 中比較常用的屬性有四個: status, statusText, ok, type.

  • status: 返回的狀態碼. 100~500+
  • statusText: 返回狀態碼代表的含義. 比如, 返回"ok".
  • ok: 用來檢差 status 是否在200和299之間.
  • type: 表示請求是否跨域, 或是否出錯. 取值為: “basic”, “cors”, “default”, “error” 或
    “opaque”.
fetch('https://www.villainhr.com/cors-enabled/some.json', {mode: 'cors',credentials:'include'}) .then(function(response) { // ... }) 

這里, 我們主要關心一下 type 上面掛載的一些屬性.

  • basic: 同域通信類別. 可以正常的訪問 response 的 header(除了 Set-Cookie 頭).
  • cors: 跨域通信類別. 一般只能訪問以下的頭:
    - Cache-Control - Content-Language - Content-Type - Expires - Last-Modified - Pragma 
  • error: 網絡錯誤類別.
  • opaque: 無法理解類別. 當使用 no-cors 發送跨域請求時,會觸發.

另外,在 response 上面,還掛載了幾個常用的方法: text(),json().

  • text(): 主要用來處理 server 返回的 string 類型數據.
  • josn(): 主要用來處理 server 返回的 json 類型數據.

使用方式都是流式 API.

fetch('https://www.villainhr.com/cors-enabled/some.json') .then(function(res) { res.text().then((text)=>{...}) res.json().then((obj)=>{...}) }) 

body 處理

我們通過 ajax 請求數據時,可能會收到,ArrayBuffer,Blob/File,string,FormData 等等。並且,在發送的時候比如:

var form = new FormData(document.getElementById('login-form')); fetch("/login", { method: "POST", body: form }) 

fetch 會自動設置相關的 Content-Type 的頭。另外,如果我們可以手動生成一個響應流(方便后面其他操作)。

var res = new Response(new File(["chunk", "chunk"], "archive.zip",{ type: "application/zip" })); 

流的處理

因為,req/res 都是以流的形式存在的,即,req/res 的 body 只能被使用一次。相當於就是一個文件從緩存讀到硬盤里面,那么原來文件就已經消失了。我們可以通過 bodyUsed 去檢查,該對象是否已經被使用。

var res = new Response("one time use"); console.log(res.bodyUsed); // false res.text().then(function(v) { console.log(res.bodyUsed); // true }); console.log(res.bodyUsed); // true res.text().catch(function(e) { console.log("Tried to read already consumed Response"); }); 

這樣做的原因主要是為了讓以后 Web 更好的處理視頻的相關數據。那如果我有時候想要使用多次,那該怎么辦?
例如,我們 Service Worker 中,使用 caches API 緩存響應,然后后面我還要將該響應返回給瀏覽器,那么這里 response 流就被使用了兩次。這里,就和普通的流操作一樣,將該流克隆一份,使用:

addEventListener('fetch', function(evt) { var sheep = new Response("Dolly"); console.log(sheep.bodyUsed); // false var clone = sheep.clone(); console.log(clone.bodyUsed); // false clone.text(); console.log(sheep.bodyUsed); // false console.log(clone.bodyUsed); // true evt.respondWith(cache.add(sheep.clone()).then(function(e) { return sheep; }); }); 

參考

基本的內容就是上述內容. 如果想更詳細參考的話, 請查閱:

This API is so Fetching!

introduction-to-fetch

原文鏈接:http://ivweb.io/topic/5855f6a873eaa3986e3f8e2f

【騰訊雲的1001種玩法】 征文活動技術文章等你來讀! 點擊查看詳情

歡迎加入QQ群:374933367,與騰雲閣原創作者們一起交流,更有機會參與技術大咖的在線分享!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM