[聚合文章] fetch body里数据为ReadableStream 解决办法

JavaScript 2015-09-13 16 阅读

转载自https://www.cnblogs.com/winyh/p/7053054.html

前端工程中发送 HTTP 请求从来都不是一件容易的事,前有骇人的 ActiveXObject ,后有 API 设计十分别扭的 XMLHttpRequest ,甚至这些原生 API 的用法至今仍是很多大公司前端校招的考点之一。

也正是如此,fetch 的出现在前端圈子里一石激起了千层浪,大家欢呼雀跃弹冠相庆恨不得马上把项目中的 $.ajax 全部干掉。然而,在新鲜感过后, fetch 真的有你想象的那么美好吗?

如果你还不了解 fetch,可以参考我的同事 @camsong 在 2015 年写的文章 《传统 Ajax 已死,Fetch 永生》

在开始「批斗」fetch之前,大家需要明确 fetch 的定位: fetch 是一个 low-level 的 API,它注定不会像你习惯的 $.ajax 或是 axios 等库帮你封装各种各样的功能或实现。 也正是因为这个定位,在学习或使用 fetch API 时,你会遇到不少的挫折。

(对于没有耐心看完全文的同学,请先记住本文的主旨不在于批评 fetch,事实上 fetch 的出现绝对是前端领域的进步体现。在了解主旨的前提下,关注 加黑 部分即可。)

发请求,比你想象的要复杂

很多人看到 fetch 的第一眼肯定会被它简洁的 API 吸引:

fetch('http://abc.com/tiger.png');

原来需要 new XMLHttpRequest 等小十行代码才能实现的功能如今一行代码就能搞定,能不让人动心吗!

但是当你真正在项目中使用时,少不了需要向服务端发送数据的过程,那么使用 fetch 发送一个对象到服务端需要几行代码呢?(出于兼容性考虑,大部分的项目在发送 POST 请求时都会使用 application/x-www-form-urlencoded 这种 Content-Type )

先来看看使用 jQuery 如何实现:

$.post('/api/add', {name: 'test'});

然后再看看 fetch 如何处理:

fetch('/api/add', {    method: 'POST',  headers: {    'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'  },  body: Object.keys({name: 'test'}).map((key) => {    return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);  }).join('&')});

等等, body 字段那一长串代码在干什么? 因为 fetch 是一个 low-level 的 API,所以你需要自己 encode HTTP 请求的 payload,还要自己指定 HTTP Header 中的 Content-Type 字段。

这样就结束了吗?如果你在自己的项目中这样发送 POST 请求,很可能会得到一个 401 Unauthorized 的结果(视你的服务端如何处理无权限的情况而定)。如果你在仔细看一遍文档,会发现原来 fetch 在发送请求时默认不会带上 Cookie!

好,我们让 fetch 带上 Cookie:

fetch('/api/add', {    method: 'POST',  credentials: 'include',  ...});

这样,一个最基础的 POST 请求才算能够发出去。

同理,如果你需要 POST 一个 JSON 到服务端,你需要这样做:

fetch('/api/add', {    method: 'POST',  credentials: 'include',  headers: {    'Content-Type': 'application/json;charset=UTF-8'  },  body: JSON.stringify({name: 'test'})});

相比于 $.ajax 的封装,是不是复杂的不是一点半点呢?

错误处理,比你想象的复杂

按理说,fetch 是基于 Promise 的 API,每个 fetch 请求会返回一个 Promise 对象,而 Promise 的异常处理且不论是否方便,起码大家是比较熟悉的了。然而 fetch 的异常处理,还是有不少门道。

假如我们用 fetch 请求一个不存在的资源:

fetch('xx.png')  .then(() => {  console.log('ok');}).catch(() => {  console.log('error');});

按照我们的惯例 console 应该要打印出 「error」才对,可事实又如何呢?有图有真相:
[图片上传失败...(image-1c4128-1512044596159)]

为什么会打印出 「ok」呢?

按照 MDN 的 说法 ,fetch 只有在遇到网络错误的时候才会 reject 这个 promise,比如用户断网或请求地址的域名无法解析等。只要服务器能够返回 HTTP 响应(甚至只是 CORS preflight 的 OPTIONS 响应),promise 一定是 resolved 的状态。

所以要怎么判断一个 fetch 请求是不是成功呢?你得用 response.ok 这个字段:

fetch('xx.png')  .then((response) => {  if (response.ok) {    console.log('ok');  } else {    console.log('error');  }}).catch(() => {  console.log('error');});

再执行一次,终于看到了正确的日志:


注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。