淺談 Fetch

wind4gis發表於2017-12-01

Fetch

參考: developers.google.com/web/updates…

現在可能還有一些很舊的程式還在使用XHR,類似下面的寫法:

const request = new XMLHttpRequest()
request.responseType = 'json'
request.open('GET', '/url', true)
request.onload = () => {
  console.log(request.response)
}
request.onerror = () => {
  console.log('shits happen!')
}
request.send(null)
複製程式碼

這樣子使用XHR進行非同步訪問、讀取資源顯得很繁瑣,相對比Fetch()允許你建立類似XHR的network訪問,但是使用更簡單而且乾淨的API,不需要多次回撥並且記住XHR複雜的API。Fetch API底層是通過Promises實現。

XMLHttpRequest

一個相對完整的XMLHttpRequest至少需要監聽兩個事件(onload、onerror)來實現成功和失敗的回撥,以及呼叫open()和send()

function reqListener() {
  var data = JSON.parse(this.responseText);
  console.log(data);
}

function reqError(err) {
  console.log('Fetch Error :-S', err);
}

var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.onerror = reqError;
oReq.open('get', './api/some.json', true);
oReq.send();
複製程式碼

Fetch

一個簡單的Fetch例子如下:

fetch('./api/some.json')
  .then(
    function(response) {
      if (response.status !== 200) {
        console.log('Looks like there was a problem. Status Code: ' +
          response.status);
        return;
      }

      // Examine the text in the response
      response.json().then(function(data) {
        console.log(data);
      });
    }
  )
  .catch(function(err) {
    console.log('Fetch Error :-S', err);
  });
複製程式碼

Fetch的語法更加語義化、比較好理解。在上面的例子裡我們先判斷response的status碼,如果是200我們才將response解析為JSON

fetch()請求返回的response是Stream物件,因此我們呼叫response.json時由於非同步讀取流物件所以返回的是一個Promise物件。

Fetch用async優化程式碼

由於Fetch底層是用Promise實現,我們可以直接用async來優化上面的程式碼,減少回撥,使其更加語義化、容易理解

async function geturl(){
	try{
		let res = await fetch('./api/some.json')
		if(res.status == 200){
			console.log(await res.text())
		}
	} catch(err){
		console.log(err)
	}
}
複製程式碼

Response後設資料

在上面的例子裡,我們瞭解了Response物件的status狀態以及怎麼把response物件轉換為JSON物件,讓我們來看看Response物件的其他後設資料:

fetch('users.json').then(function(response) {
    console.log(response.headers.get('Content-Type'));
    console.log(response.headers.get('Date'));

    console.log(response.status);
    console.log(response.statusText);
    console.log(response.type);
    console.log(response.url);
});
複製程式碼

Response型別

當我們發起一個Fetch請求時,返回的response響應會自帶一個response.type屬性(basic、cors、opaque)。response.type屬性說明了非同步資源的來源,同時還有相應的處理方式。

當我們發起一個同源請求時,response.type為basic,而且你可以從response讀取全部資訊。

如果我們訪問一個非同源域名,並且有返回相應的CORs響應頭時,那麼該請求型別是cors。cors和basic很相似,就除了cors響應裡你無法訪問Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma

當我們對一個不同源的域名發起請求時,如果返回的響應頭部沒有CORS資訊,那麼這個response對應的型別就是opaque型別。一個opaque響應是無法讀取返回的資料、狀態,甚至無法確定這個請求是否成功。

我們可以自定義Fetch請求的模式,要求返回對應型別的響應,有以下幾種響應:

  1. same-origin 只返回同源請求,其他型別會被reject
  2. cors 接收同源、非同源請求,返回有CORs頭部的響應
  3. cors-with-forced-preflight 在發出請求前會先做一次安全性檢查
  4. no-cors 用來發起沒有CORS頭部並且非同源請求,並且會返回opaque響應。但是目前這種型別只能在Service Worker裡使用,在window.fetch裡不能用
fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})
  .then(function(response) {
    return response.text();
  })
  .then(function(text) {
    console.log('Request successful', text);
  })
  .catch(function(error) {
    log('Request failed', error)
  });
複製程式碼

承諾鏈

因為Fetch返回的response是基於Promise實現,所以我們可以像鏈條一樣把幾個Promise串接起來,如下所示:

function status(response) {
  if (response.status >= 200 && response.status < 300) {
    return Promise.resolve(response)
  } else {
    return Promise.reject(new Error(response.statusText))
  }
}

function json(response) {
  return response.json()
}

fetch('users.json')
  .then(status)
  .then(json)
  .then(function(data) {
    console.log('Request succeeded with JSON response', data);
  }).catch(function(error) {
    console.log('Request failed', error);
  });
複製程式碼

當然了,我們也可以用async進行程式碼優化

async function geturl(url){
	try {
		let res = await fetch(url)
		if(res.status >= 200 && res.status < 300){
			console.log('Request succeeded with JSON response', await res.json())
		}
	}catch (err){
		console.log(err)
	}
}

geturl('users.json')
複製程式碼

Post請求

當我們使用Fetch發起Post請求時,需要手動設定method引數和body引數,如下:

fetch(url, {
    method: 'post',
    headers: {
      "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
    },
    body: 'foo=bar&lorem=ipsum'
  })
  .then(json)
  .then(function (data) {
    console.log('Request succeeded with JSON response', data);
  })
  .catch(function (error) {
    console.log('Request failed', error);
  });
複製程式碼

如果沒有顯式指定method引數,那麼預設Get請求

帶Cookie傳送請求

如果我們想要在非同步請求中帶上cookie引數,那麼需要顯式指定credentials引數:

fetch(url, {
  credentials: 'include'
})
複製程式碼