Skip to content

fetch 的使用

fetch 是一种新型的浏览器 API,用于发送网络请求,与传统的 AJAX 相比,fetch 更加的简单,并且具备现代化的功能(也就是不支持低版本浏览器)

fetch 的配置

bash
let promise实例(p) = fetch(请求地址,配置项)
  • 当请求成功,p 的状态是 fulfilled,值是请求回来的内容,如果请求失败,p 的状态是 rejected,值是失败的原因
  • fetch 和 axios 有一个不一样的地方:
    • 在 fetch 中,只要服务器有反馈信息(不论 HTTP 状态码是多少),都说明网络请求成功,最后的视频 p 都是 fullfilled,只有服务器没有任何反馈(例如:请求终端、请求超时、断网等),实例 p 才是 rejected 状态
    • 在 axios 中,只有返回的状态码是以 2 开头的,才会让实例是成功态
  • fetch 配置项
    • method:请求的方法,默认是 GET 方法[GET、HEAD、DELETE、OPTIONS、POST、PUT、PATCH]
    • cache:缓存模式[*default、no-cache、reload、force-cache、only-if-cached]
    • credentials:资源凭证(例如 cookie)[includes,*same-origin,omit],fetch 默认情况下,跨域请求中,是不允许携带资源凭证的,只有同源下才允许
      • includes:同源和跨域下都可以
      • same-origin:只有同源才可以
      • omit:都不可以
    • headers:普通对象{}/Headers 实例
      • 自定义请求头信息
    • body:设置请求主体信息
      • body 只适用于 POST 系列请求,在 GET 系列请求中设置 body 会报错(让返回的实例变为失败态)
      • body 内容的格式是有要求的,并且需要指定 Content-Type 请求头信息
      • JSON 字符串 application/json {name:'xxx',age:'xxx'}
      • URLENCODED 字符串 application/x-www-form-urlencoded xxx=xxx&xxx=xxx
      • 普通字符串 text/plain
      • FormData 对象 multipart/form-data 主要运用在文件上传或者表单提交的操作中
    • 相比较 axios 来说,fetch 没有对 GET 系列请求的问号传参的信息做特殊的处理,需要手动拼接到 URL 的末尾才可以

fetch 发送请求

fetch 返回格式

fetch 发送请求成功之后,返回的是一个 Response 对象,它对应的就是服务器返回的数据。数据需要通过异步的方式获取,因为它返回的是一个 Promise 对象,也有一些同步的属性可以直接获取。

Response.ok

ok属性返回的是一个布尔值,表示请求是否成功,true对应的 HTTP 请求的状态码是 200-299,false对应的是其他的 HTTP 状态码

Response.status

status属性返回的是一个 HTTP 的状态码

Response.statusText

statusText属性返回的是一个字符串,表示 HTTP 回应的信息,比如成功后返回的是 OK

Response.url

url属性返回的是请求的 URL

Response.type

type属性返回请求的类型,其值有下:

  • basic:同源请求
  • cors:跨域请求
  • error:网络错误
  • opaque:如果 fetch()请求的type属性设置为no-cors,就会返回这个值
  • opaqueredicret:如果 fetch()请求的 redirect 属性设置为manual,就会返回这个值

Response.headers

headers属性指向一个 Headers 对象,对应了 HTTP 回应的所有标头,我们可以自己使用 for...of 方法循环 headers,也可以使用 headers 对象提供的方法来操作:

  • get():通过键名返回获取的键值
  • has():返回一个布尔值,表示是否包含某个标头
  • set():设置新的键值,如果该键名不存在则添加
  • append():添加标头
  • delete():删除标头
  • keys():遍历所有的键名
  • values():返回所有的键值
  • entries():返回键值对
  • forEach():循环

读取 Response 返回的数据

  • response.text():得到文本字符串
  • response.json():得到 JSON 对象
  • response.blob():得到二进制 Blob 对象
  • response.formData():得到 FormData 表单对象
  • response.arrayBuffer():得到二进制 ArrayBuffer 对象

get 请求

js
fetch('http://localhost:3000/api')
  .then(res => {
    return res.text()
  })
  .then(res => {
    return res
  })

post 请求

js
fetch('http://localhost:3000/api', {
  method: 'POST',
  headers: {
    Authentication: 'secret',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'dylan'
  })
})
  .then(res => {
    return res.json()
  })
  .then(res => {
    return res
  })

中止请求

fetch 请求想要中止的话需要一个特殊的内置对象:AbortController,它不仅可以中止fetch,还可以中止其他异步任务

js
// 创建一个控制器
const ctrol = new AbortController()
fetch('http://localhost:3000/api', {
  // 请求中断的信号
  signal: ctrol.signal
})
  .then(response => {
    const { status, statusText } = response
    if (/^(2|3)\d{2}$/.test(status)) return response.json()
    return Promise.reject({
      code: -100,
      status,
      statusText
    })
  })
  .then(value => {
    // 最终的结果
    return value
  })
  .catch(reason => {
    console.dir(reason)
  })

// 立即中断请求
ctrol.abort()

对 fetch 封装

js
/*
 http([config])
   + url 请求地址
   + method 请求方式  *GET/DELETE/HEAD/OPTIONS/POST/PUT/PATCH
   + credentials 携带资源凭证  *include/same-origin/omit
   + headers:null 自定义的请求头信息「格式必须是纯粹对象」
   + body:null 请求主体信息「只针对于POST系列请求,根据当前服务器要求,如果用户传递的是一个纯粹对象,我们需要把其变为urlencoded格式字符串(设定请求头中的Content-Type)...」
   + params:null 设定问号传参信息「格式必须是纯粹对象,我们在内部把其拼接到url的末尾」
   + responseType 预设服务器返回结果的读取方式  *json/text/arrayBuffer/blob
   + signal 中断请求的信号
 -----
 http.get/head/delete/options([url],[config])  预先指定了配置项中的url/method
 http.post/put/patch([url],[body],[config])  预先指定了配置项中的url/method/body
 */

// 判断类型
import _ from '../utils'

// 核心方法
const request = function request(config) {
  // 判断是否是对象
  if (!_.isPlainObject(config)) config = {}
  config = Object.assign(
    {
      url: '',
      method: 'GET',
      credentials: 'include',
      headers: null,
      body: null,
      params: null,
      responseType: 'json',
      signal: null
    },
    config
  )
  if (!config.url) throw new TypeError('url must be required')
  if (!_.isPlainObject(config.headers)) config.headers = {}
  if (config.params !== null && !_.isplainObject(config.params)) config.params = null

  let { url, method, credentials, headers, body, params, responseType, signal } = config
  // 处理问号传参
  if (params) {
    url += `${url.includes('?') ? '&' : '?'}${qs.stringify(params)}`
  }

  // 处理请求主题信息:按照我们后台要求,如果传递的是一个普通对象,我们要把其设置为urlencoded格式「设置请求头」?
  if (_.isPlainObject(body)) {
    body = qs.stringify(body)
    headers['Content-Type'] = 'application/x-www-form-urlencoded'
  }

  // 类似与axios中的请求请求拦截器:每一个请求,传递给服务器相同的内容可以在这里处理
  const token = localStorage.getItem('token')
  if (token) {
    headers.authorization = token
  }

  // 发送请求
  method = method.toUpperCase()
  config = {
    method,
    credentials,
    headers,
    cache: 'no-cache',
    signal
  }

  if (/^(POST|PUT|PATCH)$/i.test(method) && body) config.body = body
  return fetch(url, config)
    .then(response => {
      const { status, statusText } = response
      if (/^2|3\d{2}$/.test(status)) {
        // 请求成功,根据预设的方式,获取需要的值
        let result
        switch (responseType.toLowerCase()) {
          case 'text':
            result = response.text()
            break
          case 'arraybuffer':
            result = response.arrayBuffer()
            break
          case 'blob':
            result = response.blob()
            break
          default:
            result = response.json()
        }
        return result
      }

      // 请求失败:HTTP状态码失败
      return Promise.reject({
        code: -100,
        status,
        statusText
      })
    })
    .catch(reason => {
      // 失败的统一处理
      return promise.reject(reason)
    })
}[
  // 快捷方法
  ('GET', 'HEAD', 'DELETE', 'OPTIONS')
]
  .forEach(item => {
    request[item.toLowerCase()] = function (url, config) {
      if (!_.isPlainObject(config)) config = {}
      config.url = url
      config.method = item
      return request(config)
    }
  })

  [('POST', 'PUT', 'PATCH')].forEach(item => {
    request[item.toLowerCase()] = function (url, body, config) {
      if (!_.isPlainObject(config)) config = {}
      config.url = url
      config.method = item
      config.body = body
      return request(config)
    }
  })

export default request

/**
 * 测试请求
 * http.get('/api/getTaskList', {
 *    params: {
 *       state
 *      }
 *  }
 *
 * http.post('/api/addTask', {
 *     task,
 *     time
 * }
 */