上一章,我们开发了一些简单的代码,这部分代码最最核心的一个方法就是buildURL,应对了把对象处理成query参数的方方面面。虽然我们现在可以发起简单的请求了,但是第一,我们无法接收到服务器的响应,哦不对,其实在浏览器层面,response已经是接收到了的,只是代码里还拿不到response,因为我们还没写。第二,post的请求还没实现。而处理拿到的response实际上就是处理响应体和响应头。实现post请求,实际上就是实现请求体和请求头。今天我们就来实现这四个点的内容。

  思考题:get请求可以发送body么?大家可以思考下,答案在结尾。不要提前看哦~

一、请求头和请求体的处理

  处理请求的body,实际上就是XMLHttpRequest的send方法,它可以接收一个body作为参数,这个参数可以是Document、XMLHttpRequestBodyInit或者null。而XMLHttpRequestBodyInit可以是 BlobBufferSource (en-US), FormDataURLSearchParams, 或者 USVString 对象。当然,我们最常用的就是传一个对象的场景,所以我们需要额外的处理一下,给传递的body的对象数据转换成JSON字符串。

  

  上图,是MDN中send方法参数的详细描述。

  那么在axios中的使用方法是这样的:

// Send a POST request
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});

  上面的代码就是axios官方文档中的一部分,下面我们来实现它,首先,我们需要一个transformRequest方法,来把post请求中的data参数的对象,转换成JSON字符串,因为这里的data是一个对象,send方法是不接受对象的,所以我们要转换成字符串,也就是文档中的USVString 类型。

function transformRequest(data) {
if (utils.isPlainObject(data)) {
return JSON.stringify(data);
}
return data;
}

  我们直接在xhr.js中实现上面的代码即可,其中isPlainObject是工具类中的一个方法,用来判断是否是一个纯对象,你也知道在js中,typeof 的结果是“object”并不代表是纯对象,也可能是表单对象,日期对象等等,这里我们需要通过isPlainObjecr判断出它是一个纯粹的如我们所知“对象”。

function isPlainObject(val) {
if (toString.call(val) !== "[object Object]") {
return false;
} var prototype = Object.getPrototypeOf(val);
return prototype === null || prototype === Object.prototype;
}

  isPlainObject的实现如上,个人理解,这里判断条件是“可以拿到原型链上的"[object Object]",又通过原型链与Object的原型同源”才会被命中条件。而isObject方法,就是简单的判断typeof,这些工具方法大家都可以去gitHub上对应的章节分支及其下的文件中找到。以后这些就不再强调和重复了。

  按照上述步骤完成后,我们发现还是传过去的并不是我们想像的那样,这是因为我们还没处理header,默认的request header是text/plain,所以服务端无法处理我们传过去的数据,这时候我们就需要来处理下header。  

function processHeaders(headers, data) {
normalizeHeaderName(headers, "Content-Type");
if (utils.isPlainObject(data)) {
if (headers && !headers["Content-Type"]) {
headers["Content-Type"] = "application/json;charset=utf-8";
}
}
return headers;
}

  上面就是处理header的部分,其实也很简单,normalizeHeaderName方法需要我们在helpers文件夹下在创建一个normalizeHeaderName文件,它的作用就是统一header的名称,你传入小写的,也会转换一下。具体的注释大家可以去自己看这里不多说。上面的代码其实就是如果是一个对象,那么就加上application的类型,很简单。我们直接写在xhr对xhrAdapter中传入的config.header做一下处理即可。

    config.headers = processHeaders(config.headers || {}, config.data);

  最后,如果我们没传data的话,但是又设置了content-type请求头,那么手动去除一下:

Object.keys(config.headers).forEach((name) => {
if (config.data === null && name.toLowerCase() === "content-type") {
delete config.headers[name];
} else {
request.setRequestHeader(name, config.headers[name]);
}
});

  至此,请求体和请求头就处理完了,我们可以正确的发起携带body的请求了。那么到此我们来简单回顾一下,其实总结起来就一句话:针对普通对象的body传递,转换成json并手动设置正确的请求头

  总结一下,默认的request header 的content-type类型是text/plain,所以,虽然我们转换了body的对象为JSON字符串,但是服务器端是不知道的,所以需要设置request header的content-type为application/json即可让服务器识别。那么我们就可以正常的拿到响应体的内容了。

  那你可能会问了,开头的时候不是说了还有其他类型么?什么表单、arrayBuffer啥的?不用设置头字段么?额。。稍安勿躁,后面见分晓。

二、响应头和响应体的处理

  上面第一小节,我们已经可以发起带body的请求,并且服务器也能根据request header正确的解析了,下面我们要做的就是来处理返回的数据。我们还是来看最开始的axios官网的例子:

  我们看到,结果是返回了一个promise。ok,这就是我们想要的东西~很简单,在xhrAdapter中包一层promise即可:

  很简单,对吧~中间的红框request.onreadystatechange绑定的方法,就是我们要核心实现的处理响应体的方法:

request.onreadystatechange = function handleLoad() {
if (request.readyState !== 4) {
return;
} const responseHeaders = parseHeaders(request.getAllResponseHeaders());
const responseData =
config.responseType && config.responseType !== "text"
? request.response
: request.responseText;
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request,
};
resolve(response);
};

  首先onreadystatechange是XMLHttpRequest实例的一个方法,可以监听响应事件,readyState也是XMLHttpRequest实例上的一个属性,它会告诉你响应的状态,这些大家可以去MDN查看,首先我们面临了第一个问题,就是我们通过XMLHttpRequest实例上的getAllResponseHeaders方法获取到的响应头其实是一个以\r\n(回车符和换行符)结尾拼接的字符串,我们需要把它们转换成对象,转换成对象的方法就需要parseHeaders辅助函数来处理了,下面我们在helpers文件夹中创建一个parseHeaders文件:

"use strict";

import utils from "../utils";

// Headers whose duplicates are ignored by node
// c.f. https://nodejs.org/api/http.html#http_message_headers
// 这是我们需要忽略的header
var ignoreDuplicateOf = [
"age",
"authorization",
"content-length",
"content-type",
"etag",
"expires",
"from",
"host",
"if-modified-since",
"if-unmodified-since",
"last-modified",
"location",
"max-forwards",
"proxy-authorization",
"referer",
"retry-after",
"user-agent",
]; /**
* Parse headers into an object
*
* ```
* Date: Wed, 27 Aug 2014 08:58:49 GMT
* Content-Type: application/json
* Connection: keep-alive
* Transfer-Encoding: chunked
* ```
*
* @param {String} headers Headers needing to be parsed
* @returns {Object} Headers parsed into an object
*/
export default function parseHeaders(headers) {
var parsed = {};
var key;
var val;
var i;
// 没有headers就返回个空对象
if (!headers) {
return parsed;
}
// 用自定义的forEach方法来遍历分割后的headers
utils.forEach(headers.split("\n"), function parser(line) {
// 这时候,line还是个字符串,i就是每一个key、value中间的位置
i = line.indexOf(":");
// 分割key,去空格
key = utils.trim(line.substr(0, i)).toLowerCase();
// 分割value,去空格
val = utils.trim(line.substr(i + 1)); if (key) {
// 如果存在key并且是重复项,则略过
if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) {
return;
}
// 如果key是set-cookie,那么用数组合并作为值
if (key === "set-cookie") {
parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]);
} else {
// 否则用逗号分隔已有的值或者直接设置值
parsed[key] = parsed[key] ? parsed[key] + ", " + val : val;
}
}
}); return parsed;
}

  其实这个代码也并不复杂,大家看注释说明就好。写好了转换代码,直接在onreadystatechange中转换一下即可。然后,还有一个需要额外处理的东西,就是responseType参数:

      const responseData =
config.responseType && config.responseType !== "text"
? request.response
: request.responseText;

  responseType的不同,会影响到返回的响应体的类型。最后,我们把这些转换后的数据,resolve出去即可:

const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request,
};
resolve(response);

  这部分resolve的内容,实际上就是axios定义的需要返回的内容:

  一模一样,对嘛~

  OK,到此为止我们完成了完整的请求响应过程。并且处理其中的部分参数以及加入了promise。目前,我们所做的事情,完成了整个axios请求最核心的主线,那么我们来总结下到现在为止,我们都做了axios中的哪些事情:

  实现的axios API如下:

axios({
method:"post",
url:"xxx",
params:{},
data:{},
responseType:"",
headers:{},
}).then((res) => {
console.log(res)
})

  还有res中的response的数据。前面说过了,再有就是promise。那么在实际的代码中呢,我们实现了发起ajax请求的一条主线,也就是从请求发起,到响应返回的过程,并且在过程中,由于json的特殊性,对此还进行了相应头字段和body的转换,再有一个实用的buildURL方法,来处理对象到query字符串的转换。哦对,再有就是我们获取到的response header是字符串,我们还要分割下,把字符串转换成一个对象的parseHeader方法。

  以上,buildURL都是可以拿到实际的项目中去使用的,我就复制到了我们项目里,爽得一批(好吧,原谅我头发不长,见识也不长)。

  好了。。。。大家注意没,上面的代码没有.catch,是的,错误处理还没写,下一章我们就来写错误处理的相关代码。

答案:

  从技术层面上讲,get是可以传body的,但是在客户端,浏览器的层面,不允许get传body,所有的get中的body都视为null。但是在服务器端的http请求中,get是可以传递body的。

  另外一个思考题:get和post请求有啥区别?   

一比一还原axios源码(二)—— 请求响应处理的更多相关文章

  1. 一比一还原axios源码(零)—— 概要

    从vue2版本开始,vue-resource就不再被vue所维护和支持,官方也推荐使用axios,所以,从我使用axios至今,差不多有四五年了,这四五年的时间只能算是熟练应用,很多内部的实现和原理不 ...

  2. 一比一还原axios源码(一)—— 发起第一个请求

    上一篇文章,我们简单介绍了XMLHttpRequest及其他可以发起AJAX请求的API,那部分大家有兴趣可以自己去扩展学习.另外,简单介绍了怎么去读以及我会怎么写这个系列的文章,那么下面就开始真正的 ...

  3. 一比一还原axios源码(三)—— 错误处理

    前面的章节我们已经可以正确的处理正确的请求,并且通过处理header.body,以及加入了promise,让我们的代码更像axios了.这一章我们一起来处理ajax请求中的错误. 一.错误处理 首先我 ...

  4. 一比一还原axios源码(四)—— Axios类

    axios源码的分析,到目前为止,算上第0章已经四章了,但是实际上,还都没有进入axios真正的主线,我们来简单回顾下.最开始我们构建了get请求,写了重要的buildURL方法,然后我们处理请求体请 ...

  5. axios源码解析 - 请求拦截器

    axios请求拦截器,也就是在请求发送之前执行自定义的函数. axios源码版本 - ^0.27.2 (源码是精简版) 平时在业务中会这样去写请求拦截器,代码如下: // 创建一个新的实例 var s ...

  6. 一比一还原axios源码(六)—— 配置化

    上一章我们完成了拦截器的代码实现,这一章我们来看看配置化是如何实现的.首先,按照惯例我们来看看axios的文档是怎么说的: 首先我们可以可以通过axios上的defaults属性来配置api. 我们可 ...

  7. axios源码解析 - 请求方法的别名实现

    axios中的创建请求方式很多,比如axios(url),axios.get(url),axios.post(url),axios.delete(url),方便快捷的api设计让axios火得一塌糊涂 ...

  8. 一比一还原axios源码(八)—— 其他功能

    到此,我们完成了axios的绝大部分的功能,接下来我们来补全一下其他的小功能. 一.withCredentials  这个参数可以可以表明是否是一个跨域的请求.那这个的使用场景是啥呢?就是我们在同域的 ...

  9. 一比一还原axios源码(五)—— 拦截器

    上一篇,我们扩展了Axios,构建了一个Axios类,然后通过这个Axios工厂类,创建真正的axios实例.那么今天,我们来实现下Axios的拦截器也就是interceptors.我们来简单看下Ax ...

随机推荐

  1. JVM学习五:性能监控工具

    一.系统性能监控 系统性能工具用于确定系统运行的整体状态,基本定位问题所在. Linux – uptime • 系统时间 • 运行时间 n 例子中为7分钟 • 连接数 n 每一个终端算一个连接 • 1 ...

  2. Java线程状态介绍

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11867086.html Java 线程状态介绍: Java官方文档中对Java线程的几种状态做 ...

  3. CABasicAnimation动画

    使用CABasicAnimation动画: CALayer *znzLayer; = [[CALayer alloc]init]; //创建不断该表CALayer的transform属性动画 CABa ...

  4. unittest基础篇1

    转自http://blog.csdn.net/huilan_same/article/details/52944782 unittest是xUnit系列框架中的一员,如果你了解xUnit的其他成员,那 ...

  5. Redis 源码简洁剖析 11 - 主 IO 线程及 Redis 6.0 多 IO 线程

    Redis 到底是不是单线程的程序? 多 IO 线程的初始化 IO 线程运行函数 IOThreadMain 如何推迟客户端「读」操作? 如何推迟客户端「写」操作? 如何把待「读」客户端分配给 IO 线 ...

  6. Kubernetes:容器资源需求与限制(约束)

    Blog:博客园 个人 A Container is guaranteed to have as much memory as it requests, but is not allowed to u ...

  7. CentOS 7 下升级OpenSSH 7.4p1到OpenSSH 8.4p1

    文章目录 一.环境介绍 二.安装配置telnet 2.1.安装telnet-server 2.2.配置telnet 2.3.配置telnet登录的终端类型 2.4.启动telnet服务 三.切换登录方 ...

  8. Nginx兼容框架的pathinfo模式与URL重写

    几乎所有的框架(ThinkPHP,Zend Framework,CI,Yii,laravel等)都会使用URL重写或者pathinfo模式,使URL看起来更美观,比如可以隐藏掉入口文件,并且有利于搜索 ...

  9. Python培训:绘制饼图或圆环图

    使用pyplot的pie()函数可以快速地绘制饼图或圆环图,pie()函数的语法格式如下所示: 该函数常用参数的含义如下. ·x:表示扇形或楔形的数据. ·explode:表示扇形或楔形离开圆心的距离 ...

  10. RENIX发送固定个数报文——网络测试仪实操

    在使用RENIX软件时,有时候我们需要发送固定个数报文,那么该如何操作呢?以下为您讲解具体操作步骤. 第一步:预约测试资源 打开Renix软件,连接机箱, 预约端口 第二步:发送固定个数的报文 选中流 ...