一、在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

三、编写请求代码

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

 四、处理请求参数

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

  1. //src/helpers/url.ts
  2. import { isDate, isPlainObject } from './util'
  3. //将特殊的字符转换回来
  4. function encode(val: string): string {
  5. return encodeURIComponent(val)
  6. .replace(/%40/g, '@')
  7. .replace(/%3A/gi, ':')
  8. .replace(/%24/g, '$')
  9. .replace(/%2C/gi, ',')
  10. .replace(/%20/g, '+')
  11. .replace(/%5B/gi, '[')
  12. .replace(/%5D/gi, ']')
  13. }
  14.  
  15. export function buildURL(url: string, params?: any): string {
  16. if (!params) {
  17. return url
  18. }
  19.  
  20. const parts: string[] = []
  21.  
  22. Object.keys(params).forEach(key => {
  23. const val = params[key]
  24. if (val === null || typeof val === 'undefined') {
  25. return
  26. }
  27. //将所有的值都转成数组
  28. let values = []
  29. if (Array.isArray(val)) {
  30. values = val
  31. key += '[]'
  32. } else {
  33. values = [val]
  34. }
  35. values.forEach(val => {
  36. if (isDate(val)) {
  37. val = val.toISOString()
  38. } else if (isPlainObject(val)) {
  39. val = JSON.stringify(val)
  40. }
  41. parts.push(`${encode(key)}=${encode(val)}`)
  42. })
  43. })
  44.  
  45. let serializedParams = parts.join('&')
  46.  
  47. if (serializedParams) {
  48. const markIndex = url.indexOf('#')
  49. //去掉哈希值
  50. if (markIndex !== -1) {
  51. url = url.slice(0, markIndex)
  52. }
  53.  
  54. url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
  55. }
  56.  
  57. return url
  58. }

  

  1. //src/helpers/util.ts
  2. const toString = Object.prototype.toString
  3.  
  4. export function isDate(val: any): val is Date {
  5. return toString.call(val) === '[object Date]'
  6. }
  7.  
  8. // export function isObject (val: any): val is Object {
  9. // return val !== null && typeof val === 'object'
  10. // }
  11.  
  12. export function isPlainObject(val: any): val is Object {
  13. return toString.call(val) === '[object Object]'
  14. }

五、处理请求的body数据

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

  1. //src/helpers/data.ts
  2. import { isPlainObject } from './util'
  3.  
  4. export function transformRequest(data: any): any {
  5. if (isPlainObject(data)) {
  6. return JSON.stringify(data)
  7. }
  8. return data
  9. }
  10.  
  11. export function transformResponse(data: any): any {
  12. if (typeof data === 'string') {
  13. try {
  14. data = JSON.parse(data)
  15. } catch (e) {
  16. // do nothing
  17. }
  18. }
  19. return data
  20. }

  

  1. //src/index.ts
  2. import { AxiosRequestConfig } from './types'
  3. import xhr from './xhr'
  4. import { buildURL } from './helpers/url'
  5. import { transformRequest } from './helpers/data'
  6. function axios(config: AxiosRequestConfig): void {
  7. processConfig(config)
  8. xhr(config)
  9. }
  10.  
  11. function processConfig(config: AxiosRequestConfig) {
  12. config.url = transformURL(config)
  13. config.data = transformRequestData(config)
  14. }
  15.  
  16. function transformURL(config: AxiosRequestConfig) {
  17. const { url, params } = config
  18. return buildURL(url, params)
  19. }
  20.  
  21. function transformRequestData(config: AxiosRequestConfig) {
  22. return transformRequest(config.data)
  23. }
  24. export default axios

六 、处理请求头

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

  1. //src/helpers/headers.ts
  2. import { isPlainObject } from './util'
  3.  
  4. function normalizeHeaderName(headers: any, normalizedName: string): void {
  5. if (!headers) {
  6. return
  7. }
  8. Object.keys(headers).forEach(name => {
  9. if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
  10. headers[normalizedName] = headers[name]
  11. delete headers[name]
  12. }
  13. })
  14. }
  15.  
  16. export function processHeaders(headers: any, data: any): any {
  17. normalizeHeaderName(headers, 'Content-Type')
  18. if (isPlainObject(data)) {
  19. if (headers && !headers['Content-Type']) {
  20. headers['Content-Type'] = 'application/json;charset=utf-8'
  21. }
  22. }
  23. return headers
  24. }

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

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

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

七、获取响应数据

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

  1. axios({
  2. method: 'post',
  3. url: '/base/post',
  4. data: {
  5. a: 1,
  6. b: 2
  7. }
  8. }).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:

  1. import { AxiosRequestConfig, AxiosPromise,AxiosResponse } from './types'
  2. export default function xhr(config: AxiosRequestConfig): AxiosPromise {
  3. return new Promise((resolve) => {
  4. const { method = 'get', url, data = null, headers,responseType } = config
  5. const request = new XMLHttpRequest()
  6. if(responseType){
  7. request.responseType = responseType
  8. }
  9. request.open(method.toUpperCase(), url, true)
  10. request.onreadystatechange = function handleLoad(){
  11. if(request.readyState !==4){
  12. return
  13. }
  14. const responseHeaders = request.getAllResponseHeaders()
  15. const responseData = responseType !== 'text' ? request.response: request.responseText
  16. const response: AxiosResponse = {
  17. data: responseData,
  18. status: request.status,
  19. statusText: request.statusText,
  20. headers: responseHeaders,
  21. config,
  22. request
  23. }
  24. resolve(response)
  25. }
  26. Object.keys(headers).forEach((name) => {
  27. if (data === null && name.toLowerCase() === 'content-type') {
  28. delete headers[name]
  29. } else {
  30. request.setRequestHeader(name, headers[name])
  31. }
  32. })
  33. request.send(data)
  34. })
  35.  
  36. }

  修改src/index.ts

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

8.1 处理响应header

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

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

  1. export function parseHeaders(headers: string): any {
  2. let parsed = Object.create(null)
  3. if (!headers) {
  4. return
  5. }
  6. headers.split('\r\n').forEach((line) => {
  7. let [key, val] = line.split(':')
  8. key = key.trim().toLowerCase()
  9. if (!key) {
  10. return
  11. }
  12. if (val) {
  13. val = val.trim()
  14. }
  15. parsed[key] = val
  16. })
  17. }

 修改xhr.ts修改responseHeaders:

8.2处理响应data

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

  1. data:"{"a":1,"b":2}"
  2. 转成:
  3. data:{
  4. a:1,
  5. b:2
  6. }

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

  1. export function transformResponse(data: any): any {
  2. if (typeof data === 'string') {
  3. try {
  4. data = JSON.parse(data)
  5. } catch (e) {
  6. //
  7. }
  8. }
  9. return data
  10. }

修改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中增加接口:

  1. export interface AxiosError extends Error {
  2. config: AxiosRequestConfig
  3. code?: string
  4. request?: any
  5. response?: AxiosResponse
  6. isAxiosError?: boolean
  7. }

  新增./src/helpers/error.ts

  1. import { AxiosRequestConfig, AxiosResponse } from '../types'
  2. export class AxiosErros extends Error {
  3. config: AxiosRequestConfig
  4. code?: string |null
  5. request?: any
  6. response?: AxiosResponse
  7. isAxiosError: boolean
  8. constructor(
  9. message: string,
  10. config: AxiosRequestConfig,
  11. code?: string |null,
  12. request?: any,
  13. response?: AxiosResponse
  14. ) {
  15. super(message)
  16. this.config = config
  17. this.code = code
  18. this.request = request
  19. this.response = response
  20. this.isAxiosError = true
  21. Object.setPrototypeOf(this,AxiosErros.prototype)
  22. }
  23. }
  24. //工程函数
  25. export function createError(
  26. message: string,
  27. config: AxiosRequestConfig,
  28. code?: string |null,
  29. request?: any,
  30. response?: AxiosResponse): AxiosErros{
  31. return new AxiosErros(message,config,code,request,response)
  32. }

  

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

  1. //src/index.ts
  2. import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from './types'
  3. import xhr from './xhr'
  4. import { buildURL } from './helpers/url'
  5. import { transformRequest, transformResponse } from './helpers/data'
  6. import { processHeaders } from './helpers/headers'
  7. function axios(config: AxiosRequestConfig): AxiosPromise {
  8. processConfig(config)
  9. return xhr(config).then((res) => {
  10. return transformResponseData(res)
  11. })
  12. }
  13.  
  14. function processConfig(config: AxiosRequestConfig) {
  15. config.url = transformURL(config)
  16. config.headers = transformHeaders(config)
  17. config.data = transformRequestData(config)
  18. }
  19.  
  20. function transformURL(config: AxiosRequestConfig) {
  21. const { url, params } = config
  22. return buildURL(url, params)
  23. }
  24.  
  25. function transformRequestData(config: AxiosRequestConfig) {
  26. return transformRequest(config.data)
  27. }
  28.  
  29. function transformHeaders(config: AxiosRequestConfig): void {
  30. const { headers, data } = config
  31. return processHeaders(headers, data)
  32. }
  33.  
  34. function transformResponseData(res: AxiosResponse) {
  35. res.data = transformResponse(res)
  36. return res
  37. }
  38. export default axios

  将src/index.ts改为:

  1. import axios from './axios'
  2. export * from './types'
  3.  
  4. 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. alter table *** add constraint *** 用法---约束

    1.主键约束:要对一个列加主键约束的话,这列就必须要满足的条件就是分空因为主键约束:就是对一个列进行了约束,约束为(非空.不重复)以下是代码   要对一个列加主键,列名为id,表名为emp 格式为:a ...

  2. cocos2d-x CCHttpRequest获取网络图片并显示

    转自:http://www.cnblogs.com/hzj730/p/3178431.html //图片结构 class imgstruct : public CCObject { public: i ...

  3. JavaScript总结(1)

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...

  4. js操作Iframe非当前最上层窗体

    如果当前窗口不是最上层窗口(比如是在Iframe中),那么就把自己变为最上层窗口.  <script language="javascript" type="tex ...

  5. CF-828C

    C. String Reconstruction time limit per test 2 seconds memory limit per test 256 megabytes input sta ...

  6. Swoole 多协议 多端口 的应用

    目录 概述 网络通信协议设计 多端口监听的使用 小结 概述 这是关于 Swoole 学习的第五篇文章:Swoole 多协议 多端口 的应用. 第四篇:Swoole HTTP 的应用 第三篇:Swool ...

  7. codeforces358D Dima and Hares【dp】

    从本质入手,这个东西影响取值的就是相邻两个哪个先取 设f[i][0/1]为前i个(i-1,i)中先取i/i-1的值(这里不算上i的贡献 转移就显然了,注意要先复制-inf #include<io ...

  8. jzoj6003. 【THUWC2019模拟2019.1.16】Square (乱搞)

    题面 题解 不难发现,如果一行最后被染色,那么这行的颜色肯定一样,如果倒数第二个被染色,那么除了被最后一个染色的覆盖的那一部分剩下的颜色肯定一样 于是题目可以转化为每一次删去一行或一列颜色相同的,问最 ...

  9. 洛谷P1829 [国家集训队]Crash的数字表格 / JZPTAB(莫比乌斯反演)

    传送门 式子好麻烦orz……大佬好腻害orz->这里 //minamoto #include<iostream> #include<cstdio> #define ll ...

  10. js的Element.scrollIntoView的学习

    1.Element.scrollIntoView()    该方法让当前元素滚动到浏览器窗口的可是区域内: 2.语法: element.scrollIntoView();//等同于element.sc ...