拒绝作一个只会用 API 的文档工程师,本文将会让你从重复造轮子的过程当中掌握 web 开发相关的基本知识,特别是 XMLHttpRequest。html
又是一篇关于 TypeScript 的分享,年末了,请容许我沉淀一下。上次用 TypeScript 重构 Vconsole 的项目 埋下了对 Axios 源码解析的梗。因而,此次分享的主题就是 如何从零用 TypeScript 重构 Axios 以及为何我要这么作。前端
笔者在用 TypeScript 重复造轮子的时候目的仍是很明确的,不只是为了用 TypeScript 养成一种好的开发习惯,更重要的是了解工具库关联的基础知识。 只有更多地注重基础知识,才能早日摆脱文档工程师的困扰。(Ps: 用 TypeScript,也是为了摆脱前端查文档的宿命!)node
本次分享包括如下内容:react
项目源码,分享可能会错过某些细节实现,须要的能够看源码,测试用例基本跑通了。想一想,5w star 的库,就这样本身实现了一遍。ios
Axios 是什么?git
Promise based HTTP client for the browser and node.jsgithub
axios 是基于 Promise 用于浏览器和 nodejs 的 HTTP 客户端,它自己具备如下特性 ( √ 表示本项目具有该特性 ):web
/src/core/dispatchRequest.ts
/src/core/dispatchRequest.ts
这里主要讲解浏览器端的 XHR 实现,限于篇幅不会涉及 node 下的 http 。若是你愿意一层一层了解它,你会发现实现 axios 仍是很简单的,来一块儿探索吧!chrome
首先来看下目录。typescript
目录与 Axios 基本保持一致,core 是 Axios
类的核心代码。adapters 是 XHR 核心实现,Cancel 是与 取消请求相关的代码。helpers 用于放经常使用的工具函数。Karma.conf.js
及 test 目录与单元测试相关。.travis.yml
用于配置 在线持续集成,另外可在 github 的 README 文件配置构建状况。
打包工具选用的是 Parcel,目的是零配置编译 TypeScript 。入口文件为 src 目录下的 index.html
,只需在 入口文件里引入 index.ts
便可完成热更新,TypeScript 编译等配置:
<body>
<script src="index.ts"></script>
</body>
复制代码
Parcel 相关:
# 全局安装
yarn global add parcel-bundler
# 启动服务
parcel ./src/index.html
# 打包
parcel build ./src/index.ts
复制代码
运行完 parcel 命令会启动一个本地服务器,能够经过 .vscode
目录下的 launch.json
配置 Vscode 调试工具。
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Lanzar Chrome contra localhost",
"url": "http://localhost:1234",
"webRoot": "${workspaceRoot}",
"sourceMaps": true,
"breakOnLoad": true,
"sourceMapPathOverrides": {
"../*": "${webRoot}/*"
}
}
]
}
复制代码
配置完成后,可断点调试,按 F5 便可开始调试。
TypeScript 总体配置和规范检测参考以下:
强烈建议开启 tslint
,安装 vscode tslint 插件 并在 .vscode
目录下的 .setting
配置以下格式:
{
"editor.tabSize": 2,
"editor.rulers": [120],
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true
},
"eslint.enable": false,
"tslint.autoFixOnSave": true,
"typescript.format.enable": true,
"typescript.tsdk": "node_modules/typescript/lib"
}
复制代码
若是有安装 Prettier需注意二者风格冲突,不管格式化代码的插件是什么,咱们的目的只有一个,就是 保证代码格式化风格统一。( 最好遵循 lint 规范 )。
ps:.vscode
目录可随 git 跟踪进版本管理,这样可让 clone 仓库的使用者更友好。
另外能够经过,vscode 的 控制面板中的问题 tab 迅速查看当前项目问题所在。
咱们时常会有想要编辑某段测试代码,又不想在项目里编写的需求(好比用 TypeScript 写一个 deepCopy 函数),不想脱离 vscode 编辑器的话,推荐使用 quokka,一款可当即执行脚本的插件。
接着像这样
({
plugins: 'jsdom-quokka-plugin',
jsdom: { html: `<div id="test">Hello</div>` }
});
const testDiv = document.getElementById('test');
console.log(testDiv.innerHTML);
复制代码
重构的思路首先是看文档提供的 API,或者 index.d.ts
声明文件。 优秀一点的源码能够看它的测试用例,通常会提供 API 相关的测试,如 Axios API 测试用例 ,本次分享实现 API 以下:
总得下来就是五类 API,比葫芦娃还少。有信心了吧,咱们来一个个"送人头"。
这些 API 能够统称为实例方法,有实例,就确定有类。因此在讲 API 实现以前,先让咱们来看一下 Axios 类。
两个属性(defaults,interceptors),一个通用方法( request ,其他的方法如,get、post、等都是基于 request,只是参数不一样 )真的不能再简单了。
export default class Axios {
defaults: AxiosRequestConfig;
interceptors: {
request: InterceptorManager;
response: InterceptorManager;
};
request(config: AxiosRequestConfig = {}) {
// 请求相关
}
// 由 request 延伸出 get 、post 等
}
复制代码
Axios 库默认导出的是 Axios 的一个实例 axios,而不是 Axios 类自己。可是,这里并无直接返回 Axios 的实例,而是将 Axios 实例方法 request 的上下文设置为了 Axios。 因此 axios 的类型是 function,不是 object。但因为 function 也是 Object 因此能够设置属性和方法。因而 axios 既能够表现的像实例,又能够直接函数调用 axios(config)
。具体实现以下:
const createInstance = (defaultConfig: AxiosRequestConfig) => {
const context = new Axios(defaultConfig);
const instance = Axios.prototype.request.bind(context);
extend(instance, Axios.prototype, context);
extend(instance, context);
return instance;
};
axios.create = (instanceConfig: AxiosRequestConfig) => {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
const axios: AxiosExport = createInstance(defaults);
axios.Axios = Axios;
export default axios;
复制代码
axios 还提供了一个 Axios 类的属性,可供别的类继承。另外暴露了一个工厂函数,接收一个配置项参数,方便使用者建立多个不一样配置的请求实例。
若是不看源码,咱们用一个类,最关心的应该是构造函数,默认设置了什么属性,以及咱们能够修改哪些属性。体如今 Axios 就是,请求的默认配置。
下面咱们来看下默认配置:
const defaults: AxiosRequestConfig = {
headers: headers(), // 请求头
adapter: getDefaultAdapter(), // XMLHttpRequest 发送请求的具体实现
transformRequest: transformRequest(), // 自定义处理请求相关数据,默认有提供一个修改根据请求的 data 修改 content-type 的方法。
transformResponse: transformResponse(), // 自定义处理响应相关数据,默认提供了一个将 respone 数据转换为 JSON格式的方法
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
validateStatus(status: number) {
return status >= 200 && status < 300;
}
};
复制代码
也就是说,若是你用 Axios ,你应该知道它有哪些默认设置。
先来看下 axios 接受的请求参数都有哪些属性,如下参数属性均是可选的。使用 TypeScript 事先定义了这些参数的类型,接下来传参的时候就能够检验传参的类型是否正确。
export interface AxiosRequestConfig {
url?: string; // 请求连接
method?: string; // 请求方法
baseURL?: string; // 请求的基础连接
xsrfCookieName?: string; // CSRF 相关
xsrfHeaderName?: string; // CSRF 相关
headers?: any; // 请求头设置
params?: any; // 请求参数
data?: any; // 请求体
timeout?: number; // 超时设置
withCredentials?: boolean; // CSRF 相关
responseType?: XMLHttpRequestResponseType; // 响应类型
paramsSerializer?: (params: any) => string; // url query 参数格式化方法
onUploadProgress?: (progressEvent: any) => void; // 上传处理函数
onDownloadProgress?: (progressEvent: any) => void; // 下载处理函数
validateStatus?: (status: number) => boolean;
adapter?: AxiosAdapter;
auth?: any;
transformRequest?: AxiosTransformer | AxiosTransformer[];
transformResponse?: AxiosTransformer | AxiosTransformer[];
cancelToken?: CancelToken;
}
复制代码
export interface AxiosRequestConfig {
url?: string; // 请求连接
method?: string; // 请求方法
baseURL?: string; // 请求的基础连接
}
复制代码
先来看下相关知识:
url,method 做为 XMLHttpRequest 中 open 方法的参数。
open 语法:
xhrReq.open(method, url, async, user, password);
url 是一个 DOMString,表示发送请求的 URL。
注意:将 null | undefined 传递给接受 DOMString 的方法或参数时一般会把其 stringifies 为 “null” | “undefined”
用原生的 open 方法传递以下参数,实际请求 URL 以下:
let xhr = new XMLHttpRequest();
// 假设当前 window.location.host 为 http://localhost:1234
xhr.open('get', ''); // http://localhost:1234/
xhr.open('get', '/'); // href http://localhost:1234/
xhr.open('get', null); // http://localhost:1234/null
xhr.open('get', undefined); // http://localhost:1234/undefined
复制代码
能够看到默认 baseURL 为 window.location.host
相似 http://localhost:1234/undefined
这种 URL 请求成功的状况是存在的。当前端动态传递 url 参数时,参数是有可能为 null
或 undefined
,若是不是经过 response 的状态码来响应操做,此时获得的结果就跟预想的不同。这让我想起了,JavaScript 隐式转换的坑,比比皆是。(此处安利 TypeScript 和 '===' 操做符)
对于这种状况,使用 TypeScript 能够在开发阶段规避这些问题。但若是是动态赋值(好比请求返回的结果做为 url 参数时),须要给值判断下类型,必要时可抛出错误或转换为其余想要的值。
接着来看下 axios url 相关,主要提供了 baseURL 的支持,能够经过 axios.defaults.baseURL
或 axios({baseURL:'...'})
const isAbsoluteURL = (url: string): boolean => {
// 一、判断是否为协议形式好比 http://
return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
};
const combineURLs = (baseURL: string, relativeURL: string): string => {
return relativeURL
? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
: baseURL;
};
const suportBaseURL = () => {
// 二、baseURL 处理
return baseURL && !isAbsoluteURL(url) ? combineURLs(baseURL, url) : url;
};
复制代码
在 axios 中 发送请求时 params 和 data 的区别在于:
params 是添加到 url 的请求字符串中的,用于 get 请求。
data 是添加到请求体(body)中的, 用于 post 请求。
axios 对 params 的处理分为赋值和序列化(用户可自定义 paramsSerializer 函数)
helpers 目录下的 buildURL
文件主要生成完整的 URL 请求地址。
XMLHttpRequest 是经过 send 方法把 data 添加到请求体的。
语法以下:
send();
send(ArrayBuffer data);
send(ArrayBufferView data);
send(Blob data);
send(Document data);
send(DOMString? data);
send(FormData data);
复制代码
能够看到 data 有这几种类型:
但愿了解 data 有哪些类型的能够看这篇
实际使用:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/server', true);
xhr.onload = function() {
// 请求结束后,在此处写处理代码
};
xhr.send(null);
// xhr.send('string');
// xhr.send(new Blob());
// xhr.send(new Int8Array());
// xhr.send({ form: 'data' });
// xhr.send(document);
复制代码
另外,在发送请求即调用 send()方法以前应该根据 data 类型使用 setRequestHeader() 方法设置 Content-Type 头部来指定数据流的 MIME 类型。
Axios 在 transformRequest
配置项里有个默认的方法用于修改请求( 可自定义 )。
const transformRequest = () => {
return [
(data: any, headers: any) => {
// ...根据 data 类型修改对应 headers
}
];
};
复制代码
axios 提供配置 HTTP 请求的方法:
export interface AxiosRequestConfig {
method?: string;
}
复制代码
可选配置以下:
接着了解下 HTTP 请求
HTTP 定义了一组请求方法, 以代表要对给定资源执行的操做。指示针对给定资源要执行的指望动做. 虽然他们也能够是名词, 但这些请求方法有时被称为 HTTP 动词. 每个请求方法都实现了不一样的语义, 但一些共同的特征由一组共享:: 例如一个请求方法能够是 safe, idempotent, 或 cacheable.
safe:说一个 HTTP 方法是安全的,是说这是个不会修改服务器的数据的方法。也就是说,这是一个对服务器只读操做的方法。这些方法是安全的:GET,HEAD 和 OPTIONS。有些不安全的方法如 PUT 和 DELETE 则不是。
idempotent:一个 HTTP 方法是幂等的,指的是一样的请求被执行一次与连续执行屡次的效果是同样的,服务器的状态也是同样的。换句话说就是,幂等方法不该该具备反作用(统计用途除外)。在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。全部的 safe 方法也都是幂等的。
cacheable:可缓存的,响应是可被缓存的 HTTP 响应,它被存储以供稍后检索和使用,从而将新的请求保存在服务器。
篇幅有限,看 MDN
axios 提供配置 HTTP 请求头的方法:
export interface AxiosRequestConfig {
headers?: any;
}
复制代码
一个请求头由名称(不区分大小写)后跟一个冒号“:”,冒号后跟具体的值(不带换行符)组成。该值前面的引导空白会被忽略。
请求头能够被定义为:被用于 http 请求中而且和请求主体无关的那一类 HTTP header。某些请求头如
Accept
,Accept-*
,If-*``容许执行条件请求。某些请求头如:Cookie
,User-Agent
和Referer
描述了请求自己以确保服务端能返回正确的响应。
并不是全部出如今请求中的 http 首部都属于请求头,例如在 POST 请求中常常出现的 Content-Length
其实是一个表明请求主体大小的 entity header,虽然你也能够把它叫作请求头。
axios 根据请求方法 设置了不一样的 Content-Type
和 Accpect
请求头。
XMLHttpRequest 对象提供的 XMLHttpRequest对象提供的.setRequestHeader()
方法为开发者提供了一个操做这两种头部信息的方法,并容许开发者自定义请求头的头部信息。
XMLHttpRequest.setRequestHeader() 是设置 HTTP 请求头部的方法。此方法必须在 open() 方法和 send() 之间调用。若是屡次对同一个请求头赋值,只会生成一个合并了多个值的请求头。
若是没有设置 Accept 属性,则此发送出 send() 的值为此属性的默认值/ 。**
安全起见,有些请求头的值只能由 user agent 设置:forbidden header names 和 forbidden response header names.
默认状况下,当发送 AJAX 请求时,会附带如下头部信息:
axios 设置代码以下:
// 在 adapters 目录下的 xhr.ts 文件中:
if ('setRequestHeader' in requestHeaders) {
// 经过 XHR 的 setRequestHeader 方法设置请求头信息
for (const key in requestHeaders) {
if (requestHeaders.hasOwnProperty(key)) {
const val = requestHeaders[key];
if (
typeof requestData === 'undefined' &&
key.toLowerCase() === 'content-type'
) {
delete requestHeaders[key];
} else {
request.setRequestHeader(key, val);
}
}
}
}
复制代码
至于能不能修改 http header,个人建议是固然不能随便修改任何字段。
有一些字段是绝对不能修改的,好比最重要的 host 字段,若是没有 host 值,http1.1 协议会认为这是一个不规范的请求从而直接丢弃。一样的若是随便修改这个值,那目的网站也返回不了正确的内容
user-agent 也不建议随便修改,有不少网站是根据这个字段作内容适配的,好比 PC 和手机确定是不同的内容。
有一些字段可以修改,好比 connection
,cache-control
等。不会影响你的正常访问,但有可能会慢一点。
还有一些字段能够删除,好比你不但愿网站记录你的访问行为或者历史信息,你能够删除 cookie,referfer 等字段。
固然你也能够自定义构造任意你想要的字段,通常没什么影响,除非 header 太长致使内容截断。一般自定义的字段都建议 X-开头。好比 X-test: lance。
只要是用户主动输入网址访问时发送的 http 请求,那这些头部字段都是浏览器自动生成的,好比 host,cookie,user-agent, Accept-Encoding 等。JS 可以控制浏览器发起请求,也能在这里增长一些 header,可是考虑到安全和性能的缘由,对 JS 控制 header 的能力作了一些限制,好比 host 和 cookie, user-agent 等这些字段,JS 是没法干预的禁止修改的消息首部。关于 HTTP 的知识实在多,这里简单谈到相关联的知识。这里埋下伏笔,后续如有更适合讲 HTTP 的例子,再延伸。
接下来的 CSRF,就会修改 headers。
与 CSRF 相关的配置属性有这三个:
export interface AxiosRequestConfig {
xsrfCookieName?: string
xsrfHeaderName?: string
withCredentials?: boolean;
}
// 默认配置为
{
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
withCredentials: false
}
复制代码
那么,先来简单了解 CSRF
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,一般缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登陆的 Web 应用程序上执行非本意的操做的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
你这能够这么理解 CSRF 攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF 可以作的事情包括:以你名义发送邮件,发消息,盗取你的帐号,甚至于购买商品,虚拟货币转帐。形成的问题包括:我的隐私泄露以及财产安全。
在他们的钓鱼站点,攻击者能够经过建立一个 AJAX 按钮或者表单来针对你的网站建立一个请求:
<form action="https://my.site.com/me/something-destructive" method="POST">
<button type="submit">Click here for free money!</button>
</form>
复制代码
要完成一次 CSRF 攻击,受害者必须依次完成两个步骤:
1.登陆受信任网站 A,并在本地生成 Cookie。
2.在不登出 A 的状况下,访问危险网站 B。
使用 JavaScript 发起 AJAX 请求是限制跨域的。 不能经过一个简单的 <form>
来发送 JSON
, 因此,经过只接收 JSON,你能够下降发生上面那种状况的可能性。
第一种减轻 CSRF 攻击的方法是禁用 cross-origin requests(跨域请求)。若是你但愿容许跨域请求,那么请只容许 OPTIONS, HEAD, GET
方法,由于他们没有反作用。不幸的是,这不会阻止上面的请求因为它没有使用 JavaScript(所以 CORS 不适用)。
HTTP 头中有一个 Referer 字段,这个字段用以标明请求来源于哪一个地址。在处理敏感数据请求时,一般来讲,Referer 字段应和请求的地址位于同一域名下。这种办法简单易行,工做量低,仅须要在关键访问处增长一步校验。但这种办法也有其局限性,因其彻底依赖浏览器发送正确的 Referer 字段。虽然 http 协议对此字段的内容有明确的规定,但并没有法保证来访的浏览器的具体实现,亦没法保证浏览器没有安全漏洞影响到此字段。而且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。(PS:可见遵循 web 标准多么重要)
最终的解决办法是使用 CSRF tokens。 CSRF tokens 是如何工做的呢?
攻击者须要经过某种手段获取你站点的 CSRF token, 他们只能使用 JavaScript 来作。 因此,若是你的站点不支持 CORS, 那么他们就没有办法来获取 CSRF token, 下降了威胁。
确保 CSRF token 不能经过 AJAX 访问到!
不要建立一个/CSRF
路由来获取一个 token, 尤为不要在这个路由上支持 CORS!
token 须要是不容易被猜到的, 让它很难被攻击者尝试几回获得。 它不须要是密码安全的。 攻击来自从一个未知的用户的一次或者两次的点击, 而不是来自一台服务器的暴力攻击。
这里有个 withCredentials
,先来了解下。
XMLHttpRequest.withCredentials 属性是一个 Boolean 类型,它指示了是否该使用相似 cookies,authorization headers(头部受权)或者 TLS 客户端证书这一类资格证书来建立一个跨站点访问控制(cross-site Access-Control)请求。在同一个站点下使用 withCredentials 属性是无效的。
若是在发送来自其余域的 XMLHttpRequest 请求以前,未设置 withCredentials 为 true,那么就不能为它本身的域设置 cookie 值。而经过设置 withCredentials 为 true 得到的第三方 cookies,将会依旧享受同源策略,所以不能被经过 document.cookie 或者从头部相应请求的脚本等访问。
// 在标准浏览器环境下 (非 web worker 或者 react-native) 则添加 xsrf 头
if (isStandardBrowserEnv()) {
// 必须在 withCredentials 或 同源的状况,才设置 xsrfHeader 头
const xsrfValue =
(withCredentials || isURLSameOrigin(url)) && xsrfCookieName
? cookies.read(xsrfCookieName)
: undefined;
if (xsrfValue && xsrfHeaderName) {
requestHeaders[xsrfHeaderName] = xsrfValue;
}
}
复制代码
对于 CSRF,须要让后端同窗,敏感的请求不要使用相似 get 这种幂等的,可是因为 Form 表单发起的 POST 请求并不受 CORS 的限制,所以能够任意地使用其余域的 Cookie 向其余域发送 POST 请求,造成 CSRF 攻击。
这时,若是有涉及敏感信息的请求,须要跟后端同窗配合,进行 XSRF-Token 认证。此时,咱们用 axios 请求的时候,就能够经过设置 XMLHttpRequest.withCredentials=true
以及设置 axios({xsrfCookieName:'',xsrfHeaderName:''})
,不使用则会用默认的 XSRF-TOKEN
和 X-XSRF-TOKEN
(拿这个跟后端配合便可)。
因此,axios 特性中,客户端支持防止 CSRF/XSRF。只是方便设置 CORF-TOKEN ,关键仍是要后端同窗的接口支持。(PS:先后端相亲相爱多重要,因此做为前端的咱们仍是尽量多了解这方面的知识)
axios 经过适配器模式,提供了支持 node.js 的 http 以及客户端的 XMLHttpRequest 的两张实现,本文主要讲解 XHR 实现。
大概的实现逻辑以下:
const xhrAdapter = (config: AxiosRequestConfig): AxiosPromise => {
return new Promise((resolve, reject) => {
let request: XMLHttpRequest | null = new XMLHttpRequest();
setHeaders();
openXHR();
setXHR();
sendXHR();
});
};
复制代码
若是逐行讲解,不如录个教程视频,建议你们直接看 adapters 目录下的 xhr.ts
,在关键地方都有注释!
data
,auth
,xsrfHeaderName
设置对应的 headerssetXHR
主要是在 request.readyState === 4
的时候对响应数据做处理以及错误处理XMLHttpRequest.send
方法返回的是一个 Promise 对象,因此支持 Promise 的全部特性。
请求拦截在 axios 应该算是一个比较骚的操做,实现很是简单。有点像一系列按顺序执行的 Promise。
直接看代码实现:
// interceptors 分为 request 和 response。
interface interceptors {
request: InterceptorManager;
response: InterceptorManager;
}
request (config: AxiosRequestConfig = {}) {
const { method } = config
const newConfig: AxiosRequestConfig = {
...this.defaults,
...config,
method: method ? method.toLowerCase() : 'get'
}
// 拦截器原理:[请求拦截器,发送请求,响应拦截器] 顺序执行
// 一、创建一个存放 [ resolve , reject ] 的数组,
// 这里若是没有拦截器,则执行发送请求的操做。
// 因为以后都是 resolve 和 reject 的组合,因此这里默认 undefined。真是骚操做!
const chain = [ dispatchRequest, undefined ]
// 二、Promise 成功后会往下传递参数,因而这里先传入合并后的参数,供以后的拦截器使用 (若是有的话)。
let promise: any = Promise.resolve(newConfig)
// 三、又是一波骚操做,完美的运用了数组的方法。咋不用 reduce 实现 promise 顺序执行呢 ?
// request 请求拦截器确定须要 `dispatchRequest` 在前面,因而 [interceptor.fulfilled, interceptor.rejected, dispatchRequest, undefined]
this.interceptors.request.forEach((interceptor: Interceptor) => {
chain.unshift(interceptor.fulfilled, interceptor.rejected)
})
// response 响应拦截器确定须要在 `dispatchRequest` 后面,因而 [dispatchRequest, undefined,interceptor.fulfilled, interceptor.rejected]
this.interceptors.response.forEach((interceptor: Interceptor) => {
chain.push(interceptor.fulfilled, interceptor.rejected)
})
// 四、依次执行 Promise( fulfilled,rejected )
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise
}
复制代码
又是对基础知识的完美运用,不管是 Promise 仍是数组的变异方法都算巧妙运用。
固然,Promise 的顺序执行还能够这样:
function sequenceTasks(tasks) {
function recordValue(results, value) {
results.push(value);
return results;
}
var pushValue = recordValue.bind(null, []);
return tasks.reduce(function(promise, task) {
return promise.then(task).then(pushValue);
}, Promise.resolve());
}
复制代码
若是不知道 XMLHttpRequest 有 absort 方法,确定会以为取消请求这种秀操做的怎么可能呢!( PS:基础知识多重要 )
const { cancelToken } = config;
const request = new XMLHttpRequest();
if (cancelToken) {
cancelToken.promise
.then(cancel => {
if (!request) {
return;
}
request.abort();
reject(cancel);
request = null;
})
.catch(err => {
console.error(err);
});
}
复制代码
至于 CancelToken
就不讲了,好奇怪的实现。没有感悟到原做者的设计真谛!
最后到了单元测试的环节,先来看下相关依赖。
用的是 karma,配置以下:
执行命令:
yarn test
复制代码
本项目是基于 jasmine
来写测试用例,仍是比较简单的。
karma 会跑 test 目录下的全部测试用例,感受测试用例用 TypeScript 来写,有点难受。由于测试原本就是要让参数多样化,然而 TypeScript 事先规定了数据类型。虽然可使用泛型来解决,可是总以为有点变扭。
不过,整个测试用例跑下来,代码强壮了不少。对于这种库来讲,仍是颇有必要的。若是须要二次重构,基于 TypeScript 和 有覆盖大部分函数的单元测试支持,应该会容易不少。
感谢能看到这里的朋友,想必也是 TypeScript 或 Axios 的粉丝,不妨相互认识一下。
仍是那句话,TypeScript 确实好用。短期内就能将 Axios 大体重构了一遍,感兴趣的能够跟着一块儿。老规矩,在分享中不会具体讲库怎么用 (想必,若是本身撸完这么一个项目,应该不用去看 API 了吧。) ,更多的是从广度拓展你们的知识点。若是对某个关键词比较陌生,这就是进步的时候了。好比笔者接下来要去深刻涉略 HTTP 了。虽然,感受目前 TypeScript 的热度好像好不是很高。好东西,老是那些不容易变的。哈,别到时候打脸了。
我变强了吗? 不扯了,听杨宗纬的 "我变了,我没变" 了。
切记,没有什么是看源码解决不了的 bug。