一、在GitHub上创建一个代码仓库

找到仓库地址:git@github.com:QianDingweiCharles/ts-axios.git

二、项目配置

本地新建一个文件夹axios

用VScode打开,通过Typescript脚手架Typescript library starter搭建项目

命令行: git clone https://github.com/alexjoverm/typescript-library-starter.git axios

cd axios

查看远程分支:git remote -v,因为没有关联所以没有任何输出

关联远程分支:git remote add origin git@github.com:QianDingweiCharles/ts-axios.git

拉取远程分支并合并到当前的代码:git pull origin master

git branch 就可以看到本地也有master分支了

git push -u origin master

三、编写请求代码

//src/index.ts
import { AxiosRequestConfig } from './types'
import xhr from './xhr'
function axios(config: AxiosRequestConfig): void {
xhr(config)
}
export default axios
//src/types/index.ts这是声明文件
export type Method = 'get' | 'GET'
| 'delete' | 'Delete'
| 'head' | 'HEAD'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
export interface AxiosRequestConfig {
url: string
method?: Method
data?: any
params?: any
}

 四、处理请求参数

4.1、参数是数组

params:{foo: ['bar,'baz'']},最终请求的url是/base/get?foo[]=bar&foo[]=baz

4.2、参数是一个对象

params:{foo:{bar:'baz'}},最终请求的url是/base/get?foo=%7B........,foo后面拼接的是{“bar”:"baz"} encode后的结果。

4.3 、参数值是一个Date类型

params:{date}最终请求的url是/base/get?data=2019-04-01......,date后面拼接的是date.toISOString()的结果

4.4 特殊字符支持

对于字符@、:、¥、,空格,[,],我们是允许出现在url中的,不希望被encode

params:{foo:'@:$'}最终请求的url是/base/get?foo=@:$+,注意,我们会吧空格转成+

4.5空值忽略

params:{foo:bar,baz:null}最终的请求url是/base/get?foo=bar

4.6丢弃url中的哈希标记

axios({method:‘get’,url: '/base/get#hash',params:{foo:'bar'}})最终请求的url是/base/get?foo=bar

4.7保留url中已经存在的参数

axios({method:‘get’,url:'/base/get?foo=bar',params:{bar:'baz'}})最终的请求url是/base/get?foo=bar&bar=baz

//src/helpers/url.ts
import { isDate, isPlainObject } from './util'
//将特殊的字符转换回来
function encode(val: string): string {
return encodeURIComponent(val)
.replace(/%40/g, '@')
.replace(/%3A/gi, ':')
.replace(/%24/g, '$')
.replace(/%2C/gi, ',')
.replace(/%20/g, '+')
.replace(/%5B/gi, '[')
.replace(/%5D/gi, ']')
} export function buildURL(url: string, params?: any): string {
if (!params) {
return url
} const parts: string[] = [] Object.keys(params).forEach(key => {
const val = params[key]
if (val === null || typeof val === 'undefined') {
return
}
//将所有的值都转成数组
let values = []
if (Array.isArray(val)) {
values = val
key += '[]'
} else {
values = [val]
}
values.forEach(val => {
if (isDate(val)) {
val = val.toISOString()
} else if (isPlainObject(val)) {
val = JSON.stringify(val)
}
parts.push(`${encode(key)}=${encode(val)}`)
})
}) let serializedParams = parts.join('&') if (serializedParams) {
const markIndex = url.indexOf('#')
//去掉哈希值
if (markIndex !== -1) {
url = url.slice(0, markIndex)
} url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
} return url
}

  

//src/helpers/util.ts
const toString = Object.prototype.toString export function isDate(val: any): val is Date {
return toString.call(val) === '[object Date]'
} // export function isObject (val: any): val is Object {
// return val !== null && typeof val === 'object'
// } export function isPlainObject(val: any): val is Object {
return toString.call(val) === '[object Object]'
}

五、处理请求的body数据

需要将请求的data进行处理,如果是普通的对象,需要转换成JSON格式的数据

//src/helpers/data.ts
import { isPlainObject } from './util' export function transformRequest(data: any): any {
if (isPlainObject(data)) {
return JSON.stringify(data)
}
return data
} export function transformResponse(data: any): any {
if (typeof data === 'string') {
try {
data = JSON.parse(data)
} catch (e) {
// do nothing
}
}
return data
}

  

//src/index.ts
import { AxiosRequestConfig } from './types'
import xhr from './xhr'
import { buildURL } from './helpers/url'
import { transformRequest } from './helpers/data'
function axios(config: AxiosRequestConfig): void {
processConfig(config)
xhr(config)
} function processConfig(config: AxiosRequestConfig) {
config.url = transformURL(config)
config.data = transformRequestData(config)
} function transformURL(config: AxiosRequestConfig) {
const { url, params } = config
return buildURL(url, params)
} function transformRequestData(config: AxiosRequestConfig) {
return transformRequest(config.data)
}
export default axios

六 、处理请求头

上一步对data进行了处理,但是content-type 为plain-text而不是application/json浏览器无法处理

//src/helpers/headers.ts
import { isPlainObject } from './util' function normalizeHeaderName(headers: any, normalizedName: string): void {
if (!headers) {
return
}
Object.keys(headers).forEach(name => {
if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
headers[normalizedName] = headers[name]
delete headers[name]
}
})
} export function processHeaders(headers: any, data: any): any {
normalizeHeaderName(headers, 'Content-Type')
if (isPlainObject(data)) {
if (headers && !headers['Content-Type']) {
headers['Content-Type'] = 'application/json;charset=utf-8'
}
}
return headers
}

在配置接口中添加headers字段,用户可以设置headers

在src/index.ts加入处理请求头逻辑:

这样就可以了吗?其实并没有,真正发送request headers 的xhr我们并没有修改,下一步,修改xhr:

七、获取响应数据

在此之前我们发送的请求都是从网络层面接手服务端返回的数据,单数代码层面并没有做任何关于返回数据的处理,我们希望能处理服务端响应的数据,并支持Promise链式调用的方式,如下:

axios({
method: 'post',
url: '/base/post',
data: {
a: 1,
b: 2
}
}).then((res:any)=> {console.log(res)})

  我们可以拿到res对象,并且我们希望该对象包括:服务端返回的数据data,HTTP状态码status,状态消息,响应头headers,请求配置对象config,以及请求的XMLHttpRequest对象实例request。

7.1、定义接口类型

在src/types/index.ts

responseType让用户定义返回的类型,AxiosResponse是回调函数resolve传出去,也就是then方法里面得到的参数。AxiosPromise是返回的promise对象。

补充知识:

onreadystatechange:XMLHttpRequest.onreadystatechange 会在 XMLHttpRequest 的readyState 属性发生改变时触发 readystatechange 事件的时候被调用。

ready​State:属性返回一个 XMLHttpRequest  代理当前所处的状态。一个 XHR 代理总是处于下列状态中的一个。

getAllResponseHeaders() :方法返回所有的响应头,以\r\n分割的字符串,或者 null 如果没有收到任何响应.

修改src/xhr.ts:

import { AxiosRequestConfig, AxiosPromise,AxiosResponse } from './types'
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve) => {
const { method = 'get', url, data = null, headers,responseType } = config
const request = new XMLHttpRequest()
if(responseType){
request.responseType = responseType
}
request.open(method.toUpperCase(), url, true)
request.onreadystatechange = function handleLoad(){
if(request.readyState !==4){
return
}
const responseHeaders = request.getAllResponseHeaders()
const responseData = responseType !== 'text' ? request.response: request.responseText
const response: AxiosResponse = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
}
resolve(response)
}
Object.keys(headers).forEach((name) => {
if (data === null && name.toLowerCase() === 'content-type') {
delete headers[name]
} else {
request.setRequestHeader(name, headers[name])
}
})
request.send(data)
}) }

  修改src/index.ts

八、处理响应header+处理响应data

8.1 处理响应header

通过XMLHTTPRequest的getAllResponseHeaders方法获取的值是一串以\r\n的字符串,我们希望最终解析成一个对象结构。

在src/helpers/headers.ts中添加函数:

export function parseHeaders(headers: string): any {
let parsed = Object.create(null)
if (!headers) {
return
}
headers.split('\r\n').forEach((line) => {
let [key, val] = line.split(':')
key = key.trim().toLowerCase()
if (!key) {
return
}
if (val) {
val = val.trim()
}
parsed[key] = val
})
}

 修改xhr.ts修改responseHeaders:

8.2处理响应data

在我们不设置responseType的情况下,当服务端返回给我们的数据是字符串类型,我们可以尝试再把他转换成一个JSON 对象,例如:

data:"{"a":1,"b":2}"
转成:
data:{
a:1,
b:2
}

 再src/helpers/data.ts中增加函数:

export function transformResponse(data: any): any {
if (typeof data === 'string') {
try {
data = JSON.parse(data)
} catch (e) {
//
}
}
return data
}

修改src/index.ts如下

  

九、异常情况处理

9.1 网络异常错误

当网络出现异常,比如不通的时候发送请求会触发XMLHttpRequest对象实例的error事件,于是我们可以在onerror的事件回调函数找那个捕获此类错误

修改src/xhr.ts如下:

9.2处理超时错误

当用户配置了超时时间时,如果超过了这个时间,那么将触发onTimeout事件。

在src/types/index.ts中的AxiosRequestConfig接口,添加timeout

修改xhr.ts

9.3 处理非200状态码

9.4错误信息增强

希望提供的错误信息不仅仅包含错误文本信息,还包括请求对象配置config、错误代码code,XMLHttpRequest对象实例request,以及自定义响应对象response。

在src/type/index.ts中增加接口:

export interface AxiosError extends Error {
config: AxiosRequestConfig
code?: string
request?: any
response?: AxiosResponse
isAxiosError?: boolean
}

  新增./src/helpers/error.ts

import { AxiosRequestConfig, AxiosResponse } from '../types'
export class AxiosErros extends Error {
config: AxiosRequestConfig
code?: string |null
request?: any
response?: AxiosResponse
isAxiosError: boolean
constructor(
message: string,
config: AxiosRequestConfig,
code?: string |null,
request?: any,
response?: AxiosResponse
) {
super(message)
this.config = config
this.code = code
this.request = request
this.response = response
this.isAxiosError = true
Object.setPrototypeOf(this,AxiosErros.prototype)
}
}
//工程函数
export function createError(
message: string,
config: AxiosRequestConfig,
code?: string |null,
request?: any,
response?: AxiosResponse): AxiosErros{
return new AxiosErros(message,config,code,request,response)
}

  

然后新建src/axios.ts从index.ts中复制所有

//src/index.ts
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from './types'
import xhr from './xhr'
import { buildURL } from './helpers/url'
import { transformRequest, transformResponse } from './helpers/data'
import { processHeaders } from './helpers/headers'
function axios(config: AxiosRequestConfig): AxiosPromise {
processConfig(config)
return xhr(config).then((res) => {
return transformResponseData(res)
})
} function processConfig(config: AxiosRequestConfig) {
config.url = transformURL(config)
config.headers = transformHeaders(config)
config.data = transformRequestData(config)
} function transformURL(config: AxiosRequestConfig) {
const { url, params } = config
return buildURL(url, params)
} function transformRequestData(config: AxiosRequestConfig) {
return transformRequest(config.data)
} function transformHeaders(config: AxiosRequestConfig): void {
const { headers, data } = config
return processHeaders(headers, data)
} function transformResponseData(res: AxiosResponse) {
res.data = transformResponse(res)
return res
}
export default axios

  将src/index.ts改为:

import axios from './axios'
export * from './types' export default axios

其他请下载:https://files.cnblogs.com/files/QianDingwei/ts-axios-doc-master.zip

基于TypeScript从零重构axios的更多相关文章

  1. 使用Typescript重构axios(三十二)——写在最后面(总结)

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  2. 使用Typescript重构axios(一)——写在最前面

    0.系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三)- ...

  3. 使用Typescript重构axios(六)——实现基础功能:获取响应数据

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  4. 使用Typescript重构axios(十三)——让响应数据支持泛型

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  5. 使用Typescript重构axios(十四)——实现拦截器

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  6. 使用Typescript重构axios(十九)——请求取消功能:实现第二种使用方式

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  7. 使用Typescript重构axios(二)——项目起手,跑通流程

    0.系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三)- ...

  8. 使用Typescript重构axios(三)——实现基础功能:处理get请求url参数

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  9. 使用Typescript重构axios(四)——实现基础功能:处理post请求参数

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

随机推荐

  1. Poj1007_DNA Sorting(面向对象方法)

    一.Description One measure of ``unsortedness'' in a sequence is the number of pairs of entries that a ...

  2. I/O:Unit1

    编程,从键盘读入学生成绩(0~100分),共15名学生,计算并显示总分.平均成绩.单的学生成绩 ; sum: avg: DATA1 SEGMENT STU DB ,,,,,,,,,,,,,, SUM ...

  3. scores

    题意: m维偏序问题. 解法: 考虑对每一维按照每一个元素在这一维的数值分块,对于每一个块维护一个大小为 n 的bitset,表示前缀/后缀满足条件的元素集合. 对于每一个询问,我们可以枚举找到相应的 ...

  4. centos6.5安装gmime-2.6

    安装gmime库所需要的依赖库: 1 libffi库安装 简介:glib安装时依赖的库 解压: tar xf libffi-3.0.0.tar.gz 进入目录: cd libffi-3.0.0 安装: ...

  5. cshtml 获取session值

    在 cshtml 中,我们如何获取 session 的呢? 首先我们先设置 session 值,我们一般都会在 Controller 设置 session. Session["isAdmin ...

  6. MS SQL JSON类型type

    在MS SQL Server 2016,已经支持JSON处理. 执行下面代码,将获取ms sql server对象类型以及其说明: IF OBJECT_ID('tempdb.dbo.#json_typ ...

  7. 死磕 java同步系列之JMM(Java Memory Model)

    简介 Java内存模型是在硬件内存模型上的更高层的抽象,它屏蔽了各种硬件和操作系统访问的差异性,保证了Java程序在各种平台下对内存的访问都能达到一致的效果. 硬件内存模型 在正式讲解Java的内存模 ...

  8. 核心容器的两个接口(ApplicationContext和BeanFactory)引发出的问题

    BeanFactory 才是Spring 容器中的顶层接口.ApplicationContext 是它的子接口. ApplicationContext 它在构建核心容器时, 创建对象采取的策略是采用立 ...

  9. TensorFlow中设置学习率的方式

    目录 1. 指数衰减 2. 分段常数衰减 3. 自然指数衰减 4. 多项式衰减 5. 倒数衰减 6. 余弦衰减 6.1 标准余弦衰减 6.2 重启余弦衰减 6.3 线性余弦噪声 6.4 噪声余弦衰减 ...

  10. 2018最新苹果APP上架App Store流程(超详细)

    本文转发:https://blog.csdn.net/xxw888/article/details/73618837 2018最新整理iOS app上架app详细教程 上架iOS需要一个付费688的开 ...