前言

客户端服务器端通信方式ajax (ajax JQ的类库 /axios类库)  /jsonp  / fetch

fetch是Es6新提供的API:基于不同于XMLHttpRequest的方式,基于客户端和服务器端的数据通信,而且本身是基于promise管理请求结果的(发送请求返回promi实例)

fetch的基础知识和管理

语法:

Promise<Response> fetch(input[, init]); :返回promise实例 input:URL地址 init:配置项

  • 基于fetch发送请求,只要服务器有响应结果,fetch都认为是成功的<promise的状态是fulfilled>
  • 默认情况下只有 断网 ,请求中断,请求超时等请况下,服务器不会有任何响应,fetch才会认为当前请求是失败的!!
  • 在成功的THEN方法中,我们需要根据HTTP状态码,手动校验成功还是失败!
  • 还需要把响应主体body中存储的数据变为想要的,一般都要json格式,并且监听是否转换成功

fetch目前不如XMLHttpRequest的机制完善(例如fetch中没有设置请求超时和请求中断的方法)

  • fetch请求超时如何处理:自己在发送请求后,设置定时器监听,如果请求先到则删掉定时器,如果定时器先到,则直接走catch失败情况
  • 对于中断请求目前有一个草案阶段的API可以实现AbortController

Response内置类

  • 返回的response对象是Response类的实例
  • status/statusText:http状态码及其描述
  • ok:状态码是200时为true
  • Response.headers 包含此 Response 所关联的 Headers 对象。
  • Response.redirected  表示该 Response 是否来自一个重定向,如果是的话,它的 URL 列表将会有多个条目。
  • Response.type  包含 Response 的类型(例如,basic、cors)是否跨域
  • Response.url  包含 Response 的URL。
  • Response.useFinalURL 包含了一个布尔值,来标示这是否是该 Response 的最终 URL Response 实现了 Body 接口,所以以下属性亦可用:
  • Body.body 一个简单的 getter,用于暴露一个 ReadableStream 类型的 body 内容。
  • Body.bodyUsed  包含了一个布尔值来标示该 Response 是否读取过 Body。

Response.prototype

这些方法的目的是:把body可读流对象解析出指定格式的数据 例如调用Response.json()会返回一个json格式对象的数据

执行方法会返回一个全新的promise,实例存储的值是想要的数据:服务器返回的响应主体内容有很多,当我们想执行某个方法转换为想要格式的时候,可能会成功,也可能会失败,所以在次基于promise管理

  • arrayBuffer() Buffer格式
  • blob()  二进制格式
  • neienformData()  文件上传 表单上传
  • json() json格式字符串
  • text()  纯字符串格式
  • Response.clone() 创建一个 Response 对象的克隆。
  • Response.error()返回一个绑定了网络错误的新的 Response 对象。
  • Response.redirect()  用另一个 URL 创建一个新的 Response。 Response 实现了 Body 接口,所以以下方法同样可用:

Headers内置类

headers:Headers内置类的实例对象(可以对请求头响应头信息做管理)

  • append /delete/ set:设置请求头/响应头
  • forEach/get/has/获取请求头/响应头
  • keys/entries/values迭代器信息

ReadableStream内置类

body:ReadableStream内置类的实例对象(可读流 存储服务器返回的响应主体信息)

init 配置项

一个配置项对象,包括所有对请求的设置。可选的参数有:

  • method: 请求方式:GET*/POST/DELETE/HEAD/PUT...
  • headers设置请求头信息 自定义请求头信息{Headers对象 & 普通对象}
  • body: 设置请求主体信息(只有post系列请求才可以设置,get系列请求会报错,格式有要求:json字符串,URLENCODED格式字符串,普通字符串,FormData格式对象,Buffer/bolb格式...不能是普通对象,并且要根据请求主体的数据格式,配置相关的请求头(Content-Type)
  • mode: 请求的模式,如 cors、 no-cors 或者 same-origin。
  • credentials: 是否允许携带资源凭证 include(同源跨域都允许)same-origin(同源才允许)omit都不允许
  • cache:  请求的 cache 模式: default、 no-store、 reload 、 no-cache 、 force-cache 或者 only-if-cached 。
  • redirect: 可用的 redirect 模式: follow (自动重定向), error (如果产生重定向将自动终止并且抛出一个错误), 或者 manual (手动处理重定向). 在Chrome中默认使用follow(Chrome 47之前的默认值是manual)。
  • referrer: 一个 USVString 可以是 no-referrer、client或一个 URL。默认是 client。
  • referrerPolicy: 指定了HTTP头部referer字段的值。可能为以下值之一: no-referrer、 no-referrer-when-downgrade、 origin、 origin-when-cross-origin、 unsafe-url 。
  • integrity: 包括请求的  subresource integrity 值 

[扫盲] 客户端基于请求主体传递给服务器的格式

  • json字符串
  • urlenccoded格式字符串
  • formData格式对象

代码

import { Message } from 'element-ui';
//创造一个中断控制器
let controller = new AbortController();
//自定义请求头信息{Headers对象 & 普通对象}
let headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded')
headers.append('name', 'lisa')
fetch('/api/getTaskList', {
    method: 'GET',//请求方式:GET*/POST/DELETE/HEAD/PUT...
    headers,//设置请求头信息
    body: '',//设置请求主体信息(只有post系列请求才可以设置,get系列请求会报错,格式有要求:json字符串,URLENCODED格式字符串,普通字符串,FormData格式对象,Buffer/bolb格式...不能是普通对象,并且要根据请求主体的数据格式,配置相关的请求头(Content-Type)
    credentials: 'include',//是否允许携带资源凭证 include(同源跨域都允许)same-origin(同源才允许)omit都不允许
    //给fetch请求设置信号器
    signal: controller.signal
})
    .then(response => {
        let { status, statusText } = response;
        if (!/^2\d{2}$/.test(status)) {
            //失败:状态码不对 
            return Promise.reject({
                code: 100,
                status,
                statusText
            });
        }
        //转换如果失败,走到catch,转换成功走下一个THEN
        return response.json().catch(resone => {
            return Promise.reject({
                code: 200,
                message: resone.message
            });
        });

    })
    .then(value => {
        console.log('成功', value);
    })
    .catch(resone => {
        //状态码不是2开始的[code=100],转换响应主体格式失败[code=200],请求中断[code=20],断网
        let { code, status } = resone || {};
        if (code === 100) {
            switch (+status) {
                case 404:
                    Message.error('请求地址错误');
                //...
            }
        } else if (code === 200) {
            Message.error('请求格式转换失败');
        } else if (code === 20) {
            Message.error('请求中断');
        } else {
            Message.error('网路出现异常');
        }
    });
//中断请求
// controller.abort();

fetch的二次封装

封装request([config])

// 核心方法

  •  url请求地址
  •  method  请求方式:GET/POST/DELETE/HEAD/PUT...
  •  credentials 携带资源凭证 include(同源跨域都允许)same-origin(同源才允许)omit都不允许
  •  headers()自定义请求头信息(格式必须是纯粹对象),根据当前服务器要求,如果用户传的是一个纯粹对象,我们需要把其变为urlencoded格式字符串,设置请求头中的Content-type...
  •  body null 请求主体信息(只针对于post系列请求,)
  • params:设置问号传参信息(格式必须是纯粹对象) 我们在其内部把其拼接到url的末尾
  • responseType 预设服务器返回结果读取方式:JSON/text/arraybuffer/blob
  • signal 中断请求的信号

// 快捷方法

  • request.get/head/delete/options([url],[config]):预先指定配置项中的url/method
  • request.post/put/patch([url],[body],[config]):预选指定了配置项中的url /method/body
  • request.abort 中断当前请求
import qs from 'qs';
import { isPlainObject } from '../assets/utils';
import { message } from 'antd';

/* 核心方法 */
const http = function http(config) {
    // init config & validate
    if (!isPlainObject(config)) config = {};
    config = Object.assign({
        url: '',
        method: 'GET',
        credentials: 'include',
        headers: null,
        body: null,
        params: null,
        responseType: 'json',
        signal: null
    }, config);
    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;

    // 处理URL:params存在,我们需要把params中的每一项拼接到URL末尾
    if (params) url += `${url.includes('?') ? '&' : '?'}${qs.stringify(params)}`;

    // 处理请求主体:只针对于POST系列请求;body是个纯粹对象,根据当前后台要求,把其变为urlencoded格式!
    if (isPlainObject(body)) {
        body = qs.stringify(body);
        headers['Content-Type'] = 'application/x-www-form-urlencoded';
    }

    // 类似于Axios的请求拦截器,例如:把存储在客户端本地的token信息携带给服务器「根据当前后台要求处理」
    let token = localStorage.getItem('token');
    if (token) headers['authorzation'] = token;

    // 发送请求
    method = method.toUpperCase();
    config = {
        method,
        credentials,
        headers,
        cache: 'no-cache',
        mode: 'cors'
    };
    if (/^(POST|PUT|PATCH)$/i.test(method) && body) config.body = body;
    if (signal) config.signal = signal;
    return fetch(url, config).then(response => {
        // 成功则返回响应主体信息
        let { status, statusText } = response,
            result;
        if (!/^(2|3)\d{2}$/.test(status)) return Promise.reject({ code: -1, status, statusText });
        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.then(null, reason => Promise.reject({ code: -2, reason }));
    }).catch(reason => {
        // 根据不同的失败情况做不同的统一提示
        /* let code = reason?.code;
        if (+code === -1) {
            // 状态码问题
            switch (+reason.status) {
                case 404:
                    // ...
                    break;
            }
        } else if (+code === -1) {
            // 读取数据出现问题
        } else if (+code === 20) {
            // 请求被中断
        } else {
            // 网络问题
        } */
        message.error('小主,当前网络出现异常,请稍后再试~~');
        return Promise.reject(reason);
    });
};

/* 快捷方法 */
['GET', 'HEAD', 'DELETE', 'OPTIONS'].forEach(item => {
    http[item.toLowerCase()] = function (url, config) {
        if (!isPlainObject(config)) config = {};
        config['url'] = url;
        config['method'] = item;
        return http(config);
    };
});
['POST', 'PUT', 'PATCH'].forEach(item => {
    http[item.toLowerCase()] = function (url, body, config) {
        if (!isPlainObject(config)) config = {};
        config['url'] = url;
        config['method'] = item;
        config['body'] = body;
        return http(config);
    };
});

export default http;