Fetch 讲解

3,440 阅读5分钟

一、概念

1. fetch基础概念

Fetch API 提供了一个获取资源的接口(包括跨域请求)。提供了对 Request 和 Response (以及其他与网络请求有关的)对象的通用定义,使之今后可以被使用到更多地应用场景中:无论是service workersCache API、又或者是其他处理请求和响应的方式,甚至是任何一种需要你自己在程序中生成响应的方式。

  • window.fetch是基于Promise设计的,更强大。
  • 现在所有的现代浏览器都在本地实现fetch函数,如果使用fetch时本身报错,那就是你使用的浏览器不支持。

2. fetch语法

语法

Promise fetch(url, param);

参数

url: 定义要获取的资源
param[可选]: 一个配置项对象,包括所有对请求的设置。可选的参数有:

  • method: 请求使用的方法,如 GET、POST。
  • headers: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
  • body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
  • mode: 请求的模式,如 cors、 no-cors 或者 same-origin。
  • credentials: 请求的 credentials,如 omit、same-origin 或者 include。为了在当前域名内自动发送 cookie
  • cache: 请求的 cache 模式: default 、 no-store 、 reload 、 no-cache 、 force-cache 或者 only-if-cached 。

请注意,fetch规范与jQuery.ajax()主要有两种方式的不同,牢记:

  • 当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
  • 默认情况下,fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)。自从2017年8月25日后,默认的credentials政策变更为same-originFirefox也在61.0b13中改变默认值

3. fetch使用

再使用之前,你先把程序跑起来,看看效果~~
Koa2服务和React服务,存在跨域限制。解决方案:

  • 开发环境下:配置proxy代理
  • 生产环境下:Nginx代理
// Koa2 中设置
// 利用 koa2-cors 解决跨域
npm install --save koa2-cors

var Koa = require('koa');
var cors = require('koa2-cors');
var app = new Koa();

// 方法1
app.use(cors({
  "Access-Control-Allow-Origin": "*"
}));

// 方法2
app.use(cors({
  origin: function(ctx) {
    if (ctx.url === '/test') {
      return false;
    }
    return '*';
  },
  exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
  maxAge: 5,
  credentials: true,
  allowMethods: ['GET', 'POST', 'DELETE'],
  allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}));
// react 中请求
useEffect(()=>{
    fetch('http://127.0.0.1:3000/string',{method:"POST"})
        .then(function(response) {
          console.log(response);
          return response.json(); // 这只是一个 HTTP 响应,而不是真的JSON。为了获取JSON的内容,需要使用 json()方法
        })
        .then(function(myJson) {
          console.log(myJson);
        });
  })
  
  "proxy": "http://127.0.0.1:3000/"

(1). 带多参数的请求 点击了解参数详细讲解

useEffect(()=>{
    fetch('http://127.0.0.1:3000/string',{
        method:"POST",
        body: JSON.stringify(data), // 将参数以JSON字符串的形式传递给后端
        credentials: 'include', // 带凭据,不论是不是跨域的请求,总是发送请求资源域在本地的 cookies、 HTTP Basic authentication 等验证信息.
        headers: {// 定义都以JSON字符串格式传递
            'Accept': 'application/json', // 接口要返回给客户端的数据格式,
            'Content-Type': 'application/json' // 客户端发送给服务器端的数据格式
        },
        mode: 'cors', // 支持跨域 no-cors, cors, *same-origin

    })
        .then(function(response) {
          console.log(response);
          return response.json(); // 这只是一个 HTTP 响应,而不是真的JSON。为了获取JSON的内容,需要使用 json()方法
        })
        .then(function(myJson) {
          console.log(myJson);
        });
  })

(2). 上传文件 可以通过HTML<input type="file" />元素,FormData() 和fetch()上传文件。

var formData = new FormData();
var fileField = document.querySelector("input[type='file']");

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

fetch('https://example.com/profile/avatar', {
  method: 'PUT',
  body: formData
})
.then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

(3). 上传多个文件 可以通过HTML<input type="file" mutiple/>元素,FormData() 和fetch()上传文件。

var formData = new FormData();
var photos = document.querySelector("input[type='file'][multiple]");

formData.append('title', 'My Vegas Vacation');
// formData 只接受文件、Blob 或字符串,不能直接传数组,所以必须循环嵌入
for (let i = 0; i < photos.files.length; i++) { 
    formData.append('photo', photos.files[i]); 
}

fetch('https://example.com/posts', {
  method: 'POST',
  body: formData
})
.then(response => response.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));

(4). 检测请求是否成功 如果遇到网络故障,fetch() promise 将会 reject,带上一个 TypeError 对象。虽然这个情况经常是遇到了权限问题或类似问题——比如 404 不是一个网络故障。想要精确的判断 fetch() 是否成功,需要包含 promise resolved 的情况,此时再判断 Response.ok 是不是为 true。类似以下代码:

fetch('flowers.jpg').then(function(response) {
  if(response.ok) {
    return response.blob();
  }
  throw new Error('Network response was not ok.');
}).then(function(myBlob) { 
  var objectURL = URL.createObjectURL(myBlob); 
  myImage.src = objectURL; 
}).catch(function(error) {
  console.log('There has been a problem with your fetch operation: ', error.message);
});

二、whatwg-fetch

1. whatwg-fetch简介

fetch挂在在BOM中,可以直接在谷歌浏览器中使用。如果不支持fetch也没有问题,可以使用第三方的ployfill来实现fetch:whatwg-fetchwhatwg-fetch,是基于window.fetchpolyfill(一个全局的垫片,我认为和es6引入时的意思类似),它实现了标准Fetch规范的一个子集。

安装

npm install whatwg-fetch --save

import 'whatwg-fetch'
window.fetch(...)

// webpack 中配置
entry: ['whatwg-fetch', ...]

2. whatwg-fetch源码解析

whatwg-fetch就是基于Promise,对XMLHttpRequest的封装,能够达到链式调用的效果。源码地址

...

// 对 Headers 封装
export function Headers(headers) {
  this.map = {}

  if (headers instanceof Headers) {
    headers.forEach(function(value, name) {
      this.append(name, value)
    }, this)
  } else if (Array.isArray(headers)) {
    headers.forEach(function(header) {
      this.append(header[0], header[1])
    }, this)
  } else if (headers) {
    Object.getOwnPropertyNames(headers).forEach(function(name) {
      this.append(name, headers[name])
    }, this)
  }
}

// 对 fetch 的封装
export function fetch(input, init) {
  return new Promise(function(resolve, reject) {
    var request = new Request(input, init)

    if (request.signal && request.signal.aborted) {
      return reject(new DOMException('Aborted', 'AbortError'))
    }

    var xhr = new XMLHttpRequest()

    function abortXhr() {
      xhr.abort()
    }

    xhr.onload = function() {
      var options = {
        status: xhr.status,
        statusText: xhr.statusText,
        headers: parseHeaders(xhr.getAllResponseHeaders() || '')
      }
      options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
      var body = 'response' in xhr ? xhr.response : xhr.responseText
      resolve(new Response(body, options))
    }

    xhr.onerror = function() {
      reject(new TypeError('Network request failed'))
    }

    xhr.ontimeout = function() {
      reject(new TypeError('Network request failed'))
    }

    xhr.onabort = function() {
      reject(new DOMException('Aborted', 'AbortError'))
    }

    xhr.open(request.method, request.url, true)

    if (request.credentials === 'include') {
      xhr.withCredentials = true
    } else if (request.credentials === 'omit') {
      xhr.withCredentials = false
    }

    if ('responseType' in xhr && support.blob) {
      xhr.responseType = 'blob'
    }

    request.headers.forEach(function(value, name) {
      xhr.setRequestHeader(name, value)
    })

    if (request.signal) {
      request.signal.addEventListener('abort', abortXhr)

      xhr.onreadystatechange = function() {
        // DONE (success or failure)
        if (xhr.readyState === 4) {
          request.signal.removeEventListener('abort', abortXhr)
        }
      }
    }

    xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
  })
}

// 封装
if (!self.fetch) {
  self.fetch = fetch
  self.Headers = Headers
  self.Request = Request
  self.Response = Response
}

...

三. axios, fetch的区别

fetch是更底层的API,axios是把这些API都做好了封装,使用更方便。我觉得和 axios 相比,fetch的可选择面更广。

  • Fetch返回的是一个未处理的Promise对象,你可以按需求自由处理。// response.json(); response.text();