这个请求库目前还有很多问题,参考了axios

写在这,主要是帮助没什么经验的兄得姐妹们参考下,大佬们轻喷

使用了Fetch和XHR

源码在 https://github.com/Janenil/http-sc

 

碰到的一些问题写在前面:

不同contentType对应不同数据结构

  • application/x-www-form-urlencoded: key=value&key=value
  • application/json: JSON.stringfy({"key", "value"})
  • multipart/form-data: new FormData(分隔符、参数描述信息等内容的构造体)
  • text/html:浏览器在获取到这种文件时会自动调用html的解析器对文件进行相应的处理
  • text/plain:

自测时碰到的一个问题

fetch:FormData上传时,期望设置,'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',或者设置:'Content-Type': 'multipart/form-data' 在查看传参的时候会出现类似这样的情况:

image

image


上网查了资料以后, 原来 post 请求上传文件的时候是不需要自己设置 Content-Type,会自动给你添加一个 boundary ,用来分割消息主体中的每个字段,如果这个时候自己设置了 Content-Type, 服务器就不知道怎么分割各个字段,因此就会报错。

请求的实体数据是经过了 multipart/form-data 算法编码同时以 utf-8 作为显示字符编码。mime-type(Content-Type) 是”multipart/form-data;” 和 运行 multipart/form-data 算法生成的”boundary=xxx” 串联在一起的字符串。

所以使用 FormData 就自动给我们规定了这些内容,不需要我们自己再去指定了。

image

所以在封装fetch的时候:

因为需要根据不同的数据类型设置不同的 Content-Type,可是上传文件的时候又不能设置 Content-Type, 所以需要谨慎地封装 fetch ,一不小心可能就会造成上面的问题。


在查这个问题的时候,又看到一个博客,也是自测时碰到的问题

测试接口时:发起post请求,控制台查看的时候时options:403

很多时候发送一个post请求,是先发送一个option请求,然后再发送post请求。

OPTIONS请求方法的主要用途有两个:

  • 获取服务器支持的HTTP请求方法;
  • 用来检查服务器的性能。

例如:AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全。

跨域请求(CORS)

options请求也称为预检请求(preflight request)。但不是所有的cors都会发生预检请求,与预检请求相对应的是简单请求(simple request)。如果是简单请求,那么请求应该符合以下条件:

  • 请求类型是GET/HEAD/POST之一
  • 请求头除了用户代理(浏览器)自带的(Connection, User-Agent)和Fetch spec as a “forbidden header name之外,用户只允许设置以下请求头:
Accept
Accept-Language
Content-Language
Content-Type (but note the additional requirements below)
Last-Event-ID
DPR
Downlink
Save-Data
Viewport-Width
Width

  • Content-Type只能是以下类型:
application/x-www-form-urlencoded
multipart/form-data
text/plain

如果不满足上面三点任何一点,那么请求都是非简单请求。对于这样的CORS请求会先发出一个预检请求,如果后端响应头(后端会返回一些字段,比如Access-Control-Allow-Method)允许这个非简单请求,那么将会发起我们实际创建的请求。

经常可能会出现的一个问题是,我们在请求头设置了Content-Type: application/json,那么这个请求就变成了非简单请求。所以会首先发起预检请求。

跨域资源共享 CORS 详解


  1. 熟悉xhr、fetch的api
  2. 熟悉xhr、fetch的参数

xhr

  • method(String): 请求所使用的HTTP方法; 例如 "GET", "POST", "PUT", "DELETE"等. 如果下个参数是非HTTP(S)的URL,则忽略该参数
  • url(String): 请求访问的URL地址
  • async(Boolean): 配置是否为异步执行,默认值为true;
  • ···

fetch

  • method(String): HTTP请求方法,默认为GET
  • body(String): HTTP的请求参数
  • headers(Object): HTTP的请求头,默认为{}
  • credentials(String): 默认为omit,忽略的意思,也就是不带cookie;还有两个参数,same-origin,意思就是同源请求带cookie;include,表示无论跨域还是同源请求都会带cookie
  • mode: 请求模式
  • headers: 请求头
  • ···
  1. 处理请求参数

image

  1. 根据不同请求处理参数
  2. 统一处理错误

xhr

  • onabort
  • onerror
  • ontimeout

fetch

  • response.ok

选择优先fetch再使用xhr

Fetch API 和 XHR 做的事情很相似,大部分情况我们通过 XHR 就已经能够完成开发任务了。

但是看对比

// 用 XHR 发起一个GET请求
var xhr = new XHMHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function() {
    console.log(xhr.response);
};
xhr.onerror = function() {
    console.log('something wrong');
};
xhr.send();
// 用 Fetch 完成同样的请求
fetch(url).then(function(response) {
    return response.json();
}).then(function(jsonData) {
    console.log(jsonData);
}).catch(function() {
    console.log('something wrong');
});

  1. MDN

Fetch 是一个现代的概念, 等同于 XMLHttpRequest。它提供了许多与XMLHttpRequest相同的功能,但被设计成更具可扩展性和高效性。

Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,以及用于初始化异步请求的 global fetch。得益于 JavaScript 实现的这些抽象好的 HTTP 模块,其他接口能够很方便的使用这些功能。 Service Workers 是一个利用了 Fetch 实现的接口的例子。 除此之外,Fetch 还利用到了请求的异步特性——它是基于 Promise 的。

目的

  1. 支持网络请求
  2. 简洁、易用的api
  3. 高度可拓展

简介

  • 提供统一promiseAPI
  • 支持浏览器环境
  • 自动转换json数据
  • 统一处理请求正常、异常(超时、请求错误、服务器)情况

支持网络请求

创建Request对象 -> 构建Request -> 发送Request -> 解析Response -> 监听回调,返回解析值

xhr介绍

  • 可以发送跨域请求,在服务端允许的情况下;
  • 支持发送和接收二进制数据;
  • 新增formData对象,支持发送表单数据;
  • 发送和获取数据时,可以获取进度信息;
  • 可以设置请求的超时时间;

fetch介绍

  • 简洁、易用、声明式
  • 基于 Promise

过程

xhr

  1. 实例化XMLHttpRequest对象获得一个实例
  2. 通过实例open一个请求,设置发送类型和接口以及同异步
  3. 如有需要配置报文,以及各种事件(success,error,timeout等)
  4. 调用实例的send方法,发送http/https的请求
  5. 服务器回调,客户端接收,并做响应处理
let xmlHttp = new XMLHttpRequest();
xmlHttp.open(method, url);
xmlHttp.send('');
xmlHttp.onreadystatechange = () => {
    // dosomething
}

fetch

因为fetch本身就是封装好的api,在这里只需要考虑:

  • 捕捉异常
  • 处理错误
  • 统一数据返回格式
  • 超时处理
  • 取消(未完成)
fetch(params.url, Object.assign({method: params.method}, {params}))
.then(res => {
    if (timeout) throw new Error('timeout!');
    checkStatus(res, reject);
})
.then(res => parseJSON(res))
.then(data => {
    let json = transformResponse(data, params.isShowError || params.isShowSystemError, params.errTipTime)
    json.success ? resolve(json.json) : reject(json.json);
})
.catch(err =>{
    errFetch(err, reject);
}) 

兼容情况

image

配置参数复用

  • 请求接口后端定义一些公共参数,每次都要传输
  • 为了一些业务,每次在http-header中设置 一些参数值
  • 统一设置接口超时时间
  • 统一错误、超时处理函数
  • 发送请求前、获得请求参数后对参数处理(未完成)
  • ....
// 配置单次请求配置
let http = new Http()
http.request("/test",{hh:5},{
    method:"post",
    timeout:5000 //超时设置为5s
})

后期添加实例配置,用于当前实例发起的所有请求。 单次配置和实例配置冲突,则会优先使用单次请求配置

export default {
    // 接口请求地址
    url: '',
    // 接口请求方式
    method: 'get',
    // fetch 请求模式
    mode: 'no-cors',
    // fetch 是否发送cookie
    credentials: 'same-origin',
    // fetch 缓存
    cache: 'no-cache',
    // fetch 重定向
    redirect: 'follow',
    // fetch 请求参数
    body: {},
    // 统一追加前缀
    baseUrl: '',
    // 请求接口中公共数据
    pulbicParams:{},
    // 接口传输数据
    data: {},
    // 请求头
    headers: {
    },
    // 请求接口中公共数据
    publicData: {},
    // 接口请求超时时间
    timeout: 20000,
    // 跨域接口请求中是否发送跨域凭证
    withCredentials: false, // default
    // 希望获得接口响应数据类型
    responseType: 'json', // default
    // 是否异步请求
    async: true,
    // 接口请求中可以发送的数据类型
    // contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
    contentType: 'application/json; charset=UTF-8',
    // 接口传输数据的补充
    params: {},
    // 是否传值为json
    isJson: true,
    // 是否展示错误
    isShowError: true,
    // 是否展示所有错误
    isShowSystemError: true,
    // 错误提示显示时间
    errTipTime: 2500,
    // serviceDiscover default config
    serviceDiscover: null,
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
};

例子

  1. Http
import Http from 'http-souche';
let http = new Http()
http({
  method: 'post',
  url: '/user/12345',
  data: {
    ID: 1111
  }
});
  1. get
import Http from 'http-souche';
let http = new Http()
http.get('/user?ID=11111')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  })
  

http.get('/user', {
      ID: 1111
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  })


// 异步请求
async function getUser() {
  try {
    const response = await http.get('/user?ID=11111');
    console.log(response);
  } catch (error) {
    console.error(error);
  }
}
  1. post
import Http from 'http-souche';
let http = new Http()
http.post('http://workflow-dev.dasouche-inc.net/v2/workFlowConfig/close.json',
{
    workFlowId: 'HvgRO4YGpv',
    token: '91563240745175281',
    bizCode: 'test'
}, {
    isJson: false
})
  1. 请求二进制

统一处理错误

  1. xhr

根据 onabort|onerror|ontimeout 判断是否请求异常

  1. fetch

根据 fetch返回response.ok 判断是否请求异常

统一错误返回格式

{
    success: false,
    code: `404`,
    msg: '未知异常',
    err: 'Unknown Exception',
    errCN: '未知异常'
}

API

Http.get(url, data, options)

发起 get 请求,url请求地址,data为请求数据,options为请求配置项。

Http.post(url, data, options)

参数意义如get

Http.request(url, data, options)

//GET请求
Http.request("/user/test" null, {method:"get"})
//DELETE 请求
Http.request("/user/test/delete", null, {method:"delete"})
//PUT请求
Http.request("/user/test/register", {name:"test"}, {method:"PUT"})
......

待优化

  • 未设置拦截器 interceptors

  • 不能中断请求

fetch中有考虑过 AbortController
但是兼容性太差
  • 兼容node
amd、cmd、umd写法的不同和如何兼容
  • 低版本兼容(ActiveXObject)

建议

  • 修改isJson命名
  • 增加请求中断

 

 
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐