1. 程式人生 > >fetch簡介: 新一代Ajax API

fetch簡介: 新一代Ajax API

AJAX半遮半掩的底層API是飽受詬病的一件事情. XMLHttpRequest 並不是專為Ajax而設計的. 雖然各種框架對 XHR 的封裝已經足夠好用, 但我們可以做得更好。更好用的API是 fetch 。下面簡單介紹 window.fetch 方法, 在最新版的 Firefox 和 Chrome 中已經提供支援。

XMLHttpRequest

在我看來 XHR 有點複雜, 我不想解釋為什麼“XML”是大寫,而“Http”是“駱峰式”寫法。使用XHR的方式大致如下:

// 獲取 XHR 非常混亂!
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
  request = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
  try {
    request = new ActiveXObject('Msxml2.XMLHTTP');
  } 
  catch (e) {
    try {
      request = new ActiveXObject('Microsoft.XMLHTTP');
    } 
    catch (e) {}
  }
}

// 開啟連線, 傳送資料.
request.open('GET', 'https://davidwalsh.name/ajax-endpoint', true);
request.send(null);

我們可以看出, XHR 其實是很雜亂的; 當然, 通過 JavaScript 框架可以很方便地使用XHR。

fetch 的基本使用

fetch 是全域性量 window 的一個方法, 第一個引數是URL:

// url (必須), options (可選)
fetch('/some/url', {
    method: 'get'
}).then(function(response) {

}).catch(function(err) {
    // 出錯了;等價於 then 的第二個引數,但這樣更好用更直觀 :(
});
// 對響應的簡單處理
fetch('/some/url').then(function(response) {

}).catch(function(err) {
    // 出錯了;等價於 then 的第二個引數,但這樣更直觀 :(
});

// 鏈式處理,將非同步變為類似單執行緒的寫法: 高階用法.
fetch('/some/url').then(function(response) {
    return //... 執行成功, 第1步...
}).then(function(returnedValue) {
    // ... 執行成功, 第2步...
}).catch(function(err) {
    // 中途任何地方出錯...在此處理 :( 
});

如果你還不習慣 then 方式的寫法,那最好學習一下,因為很快就會全面流行。

請求頭(Request Headers)

自定義請求頭資訊極大地增強了請求的靈活性。我們可以通過 new Headers() 來建立請求頭:

// 建立一個空的 Headers 物件,注意是Headers,不是Header
var headers = new Headers();

// 新增(append)請求頭資訊
headers.append('Content-Type', 'text/plain');
headers.append('X-My-Custom-Header', 'CustomValue');

// 判斷(has), 獲取(get), 以及修改(set)請求頭的值
headers.has('Content-Type'); // true
headers.get('Content-Type'); // "text/plain"
headers.set('Content-Type', 'application/json');

// 刪除某條請求頭資訊(a header)
headers.delete('X-My-Custom-Header');

// 建立物件時設定初始化資訊
var headers = new Headers({
    'Content-Type': 'text/plain',
    'X-My-Custom-Header': 'CustomValue'
});

可以使用的方法包括: append, has, get, set, 以及 delete 。需要建立一個 Request 物件來包裝請求頭:

var request = new Request('/some-url', {
    headers: new Headers({
        'Content-Type': 'text/plain'
    })
});

fetch(request).then(function() { /* handle response */ });

下面介紹 ResponseRequest 的使用方法!

Request 簡介

Request 物件表示一次 fetch 呼叫的請求資訊。傳入 Request 引數來呼叫 fetch, 可以執行很多自定義請求的高階用法:

  • method - 支援 GET, POST, PUT, DELETE, HEAD
  • url - 請求的 URL
  • headers - 對應的 Headers 物件
  • referrer - 請求的 referrer 資訊
  • mode - 可以設定 cors, no-cors, same-origin
  • credentials - 設定 cookies 是否隨請求一起傳送。可以設定: omit, same-origin
  • redirect - follow, error, manual
  • integrity - subresource 完整性值(integrity value)
  • cache - 設定 cache 模式 (default, reload, no-cache)

Request 的示例如下:

var request = new Request('/users.json', {
    method: 'POST', 
    mode: 'cors', 
    redirect: 'follow',
    headers: new Headers({
        'Content-Type': 'text/plain'
    })
});

// 使用!
fetch(request).then(function() { /* handle response */ });

只有第一個引數 URL 是必需的。在 Request 物件建立完成之後, 所有的屬性都變為只讀屬性. 請注意, Request 有一個很重要的 clone 方法, 特別是在 Service Worker API 中使用時 —— 一個 Request 就代表一串流(stream), 如果想要傳遞給另一個 fetch 方法,則需要進行克隆。

fetch 的方法簽名(signature,可理解為配置引數), 和 Request 很像, 示例如下:

fetch('/users.json', {
    method: 'POST', 
    mode: 'cors', 
    redirect: 'follow',
    headers: new Headers({
        'Content-Type': 'text/plain'
    })
}).then(function() { /* handle response */ });

因為 Request 和 fetch 的簽名一致, 所以在 Service Workers 中, 你可能會更喜歡使用 Request 物件。關於 ServiceWorker 的相關部落格請等待後續更新!

Response 簡介

Response 代表響應, fetchthen 方法接收一個 Response 例項, 當然你也可以手動建立 Response 物件 —— 比如在 service workers 中可能會用到. Response 可以配置的引數包括:

  • type - 型別,支援: basic, cors
  • url
  • useFinalURL - Boolean 值, 代表 url 是否是最終 URL
  • status - 狀態碼 (例如: 200, 404, 等等)
  • ok - Boolean值,代表成功響應(status 值在 200-299 之間)
  • statusText - 狀態值(例如: OK)
  • headers - 與響應相關聯的 Headers 物件.
// 在 service worker 測試中手動建立 response
// new Response(BODY, OPTIONS)
var response = new Response('.....', {
    ok: false,
    status: 404,
    url: '/'
});

// fetch 的 `then` 會傳入一個 Response 物件
fetch('/')
    .then(function(responseObj) {
        console.log('status: ', responseObj.status);
    });

Response 提供的方法如下:

  • clone() - 建立一個新的 Response 克隆物件.
  • error() - 返回一個新的,與網路錯誤相關的 Response 物件.
  • redirect() - 重定向,使用新的 URL 建立新的 response 物件..
  • arrayBuffer() - Returns a promise that resolves with an ArrayBuffer.
  • blob() - 返回一個 promise, resolves 是一個 Blob.
  • formData() - 返回一個 promise, resolves 是一個 FormData 物件.
  • json() - 返回一個 promise, resolves 是一個 JSON 物件.
  • text() - 返回一個 promise, resolves 是一個 USVString (text).

處理 JSON響應

假設需要請求 JSON —— 回撥結果物件 response 中有一個json()方法,用來將原始資料轉換成 JavaScript 物件:

fetch('https://davidwalsh.name/demo/arsenal.json').then(function(response) { 
    // 轉換為 JSON
    return response.json();
}).then(function(j) {
    // 現在, `j` 是一個 JavaScript object
    console.log(j); 
});

當然這很簡單 , 只是封裝了 JSON.parse(jsonString) 而已, 但 json 方法還是很方便的。

處理基本的Text / HTML響應

JSON 並不總是理想的請求/響應資料格式, 那麼我們看看如何處理 HTML或文字結果:

fetch('/next/page')
  .then(function(response) {
    return response.text();
  }).then(function(text) { 
    // <!DOCTYPE ....
    console.log(text); 
  });

如上面的程式碼所示, 可以在 Promise 鏈式的 then 方法中, 先返回 text() 結果 ,再獲取 text 。

處理Blob結果

如果你想通過 fetch 載入影象或者其他二進位制資料, 則會略有不同:

fetch('flowers.jpg')
    .then(function(response) {
      return response.blob();
    })
    .then(function(imageBlob) {
      document.querySelector('img').src = URL.createObjectURL(imageBlob);
    });

響應 Body mixin 的 blob() 方法處理響應流(Response stream), 並且將其讀完。

提交表單資料(Posting Form Data)

另一種常用的 AJAX 呼叫是提交表單資料 —— 示例程式碼如下:

fetch('/submit', {
    method: 'post',
    body: new FormData(document.getElementById('comment-form'))
});

提交 JSON 的示例如下:

fetch('/submit-json', {
    method: 'post',
    body: JSON.stringify({
        email: document.getElementById('email').value
        answer: document.getElementById('answer').value
    })
});

非常非常簡單, 媽媽再也不用擔心我的Ajax!

未完的故事(Unwritten Story)

fetch 是個很實用的API , 當前還不允許取消請求, 這使得很多程式設計師暫時不會考慮它。

新的 fetch API 比起 XHR 更簡單也更智慧。畢竟,它就是專為AJAX而設計的, 具有後發優勢. 而我已經迫不及待地使用了, 即使現在相容性還不是那麼好!

翻譯時間: 2016年5月22日

原文時間: 2016年4月15日