js Fetch返回數據res.json()報錯問題

前言

一直以來在簡單的場景中經常使用fetch代替第三方請求庫, fetch是JavaScript的原生函數, 簡單、高效、快速、穩定、可訂製等等諸多優點。一直也是用著很是舒服,直到有一天它竟然報錯了。

什麼是Fetch?

官方: Fetch API 提供了一個 JavaScript 介面,用於訪問和操縱 HTTP 管道的一些具體部分,例如請求和響應。它還提供了一個全局 fetch() 方法,該方法提供了一種簡單,合理的方式來跨網路非同步獲取資源。

還原現場

因為業務需求簡單,這裡只封裝了get和post方法, 並且後端數據都是已默認的json格式返回

const http = {
  apiBaseUrl: config.apiBaseUrl,
  get: function (url) {
    return new Promise((resolve, reject) => {
      fetch(this.apiBaseUrl + url, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        }
      }).then(res => res.json()).then(res => {
        resolve(res);
      }).catch(e => {
        console.error("請求失敗了,詳細資訊:" + JSON.stringify(e));
        reject(e);
      });
    })
  },
  post: function (url, body) {
    return new Promise((resolve, reject) => {
      fetch(this.apiBaseUrl + url, {
        method: "POST",
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        }, body: JSON.stringify(body)
      }).then(res => res.json()).then(res => {
        resolve(res);
      }).catch(e => {
        console.error("請求失敗了,詳細資訊:" + JSON.stringify(e));
        reject(e);
      })
    })
  }
}

這樣的封裝貌似也沒什麼問題 😃。

後端沒有做統一的返回數據格式約定, 直接使用.net中的ActionResult結構返回, .net封裝的應該都是很好用的 😃

/// <summary>
/// 模擬處理數據
/// </summary>
/// <returns></returns>
[HttpPost("handle-data")]
public ActionResult HandleData(string mockData)
{
    // 業務邏輯
    // ......
    
    // 處理完成,直接告訴瀏覽器我Ok了, 狀態200
    return Ok();
}

此時前端在處理完成後, 控制檯面板中 Network 中也顯示post成功返回200了, 但是在 Console 中卻有一條紅色報錯資訊

請求失敗了,詳細資訊:{}

fetch error
看到報錯位置竟然是post中的catch, 可明明在Network中看到返回200了啊, 稍作鎮靜之後就意識到應該就是返回時數據處理報錯了, 在resolve(res)上面列印也沒走這個邏輯, 那就是 上一層 .then(res=> res.json()有問題。

// 將.then(res=> res.json()) 替換成下面
.then(res => {
   console.log(res.json());
   return res.json();
})

這樣寫同樣報錯

可以看到時解析json報錯了, 嗯, 因為我們就是沒有返回任何數據, 解析自然會報錯!
既然沒有值Json解析報錯, 那解決辦法自然就得加一層判斷了(也可以讓後端必須返回一個Json, 簡單粗暴, 哈哈! ) , 思路是先讀取值然後判斷是否為空.
但是列印res時

// 將.then(res=> res.json()) 替換成下面
.then(res => {
   console.log(res);
   let arrayBuffer = res.json();
   let json = res.json();
   return res.json();
})
body: ReadableStream
         locked: true
         [[Prototype]]: ReadableStream
bodyUsed: true
headers: Headers
[[Prototype]]: Headers
ok: true
redirected: false
status: 200
statusText: "OK"
type: "cors"
url: "'//localhost:5069/api/handle-data"
[[Prototype]]: Response

而且還有一條報錯資訊

Uncaught (in promise) TypeError: Failed to execute 'json' on 'Response': body stream already read

body stream already read說明流只能讀取一次,
body是一個ReadableStream數據流,必須先讀取流才能看到數據, 那就看一下是否還能轉換成其他格式的數據.
查找MDN //developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch#body

Body 類定義了以下方法(這些方法都被 Request 和 Response所實現)以獲取 body 內容。這些方法都會返回一個被解析後的 Promise 對象和數據。

可知有5種數據格式,因為json和text可使用js原生方法JSON.parse/JSON.stringify相互轉換, 那就直接選用.text()轉成字元串判斷即可.

// 將.then(res=> res.json()) 替換成下面
.then(res => {
  let data = res.text();//轉成字元串判斷
  return data.then(r => {
  if (r.length === 0) return null;
  else return JSON.parse(r);
  })
})

驗證結果正確, 一切又回到了正常。

簡單封裝fetch 獲取json或空數據

let checkStatus = res => {
  if (res.status >= 200 && res.status < 300) return res;
  else {
    let err = new Error(res.statusText);
    err.response = res;
    throw err;
  }
}
let parseJson = res => {
  let data = res.text();
  return data.then(r => {
    if (r.length === 0) return null;
    else return JSON.parse(r);
  })
}
const http = {
  apiBaseUrl: config.apiBaseUrl,
  get: function (url) {
    return new Promise((resolve, reject) => {
      fetch(this.apiBaseUrl + url, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        }
      }).then(checkStatus).then(parseJson).then(res => {
        resolve(res);
      }).catch(e => {
        console.error("請求失敗了,詳細資訊:" + JSON.stringify(e));
        reject(e);
      });
    })
  },
  post: function (url, body) {
    return new Promise((resolve, reject) => {
      fetch(this.apiBaseUrl + url, {
        method: "POST",
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        }, body: JSON.stringify(body)
      }).then(checkStatus).then(parseJson).then(res => {
        resolve(res);
      }).catch(e => {
        console.error("請求失敗了,詳細資訊:" + JSON.stringify(e));
        reject(e);
      })
    })
  }
}

嗯 , 前端能處理的問題就不麻煩後端了 😃

總結

fetch返回的是數據流, 最終是什麼格式數據需要我們自己判斷使用, arrayBuffer/blob/formData/json/text 這些格式都有其使用場景, 前端er不要將思維只限定在了json哦。

作者:wwmin
出處://www.cnblogs.com/cnwwm
聯繫:[email protected] 微信:w_wmin

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利。如有問題或建議,請多多賜教,非常感謝。

Tags: