【typescript】用ts一步步重构简易axios(带思考)

背景

  • typescript写法比较麻烦,开发库的时候效果特别棒,对于各种逻辑组合而产生的结果可以进行有效预测,让写出来的库更好用。同时用ts书写也方便大家对于这个库使用的理解。
  • 此次从手写axios入手,一步步实现。

准备工作

  • 为了方便,直接使用react的ts脚手架,没安装的可以安装下npm i create-react-app -g,然后create-react-app 项目名 --typescript
  • 另外还需要安装原版axios做对比,以及qs用来转换对象格式的,还有parse-headers转换请求头。npm i axios @types/axios qs @types/qs parse-headers,它可能会警告说axios的声明文件已经包含在axios库里了,不需要另外安装它的声明文件,那就@types/axios不装也没事,不会报错。到时候会优先从axios的index.d.ts里去找声明文件。

流程

一、包装ajax,实现axios基础功能

  • 前面我写了个文章axios与fetch常见封装方法,里面发现这个axios实际就是用ajax发个请求,然后前面弄个数组传请求拦截方法后面弄个数组传响应拦截方法,再把方法写的好装卸一点,暴露api出去给大伙用,其实也没想象的多高级。
  • 服务端用上面文章那个即可。
  • 首先,需要做一个数据,比如用户名密码,使用原生axios发给服务器:
import axios,{AxiosResponse} from 'axios';
const baseURL = "http://localhost:8080";

interface User{
    name:string
    password: string
}
let user : User ={
    name:'yehuo',
    password:'123456'
}
axios({
    method:'get',
    url:baseURL+'/get',
    params:user
}).then((response:AxiosResponse)=>{
    console.log(response);
    return response.data
}).catch(err=>{
    console.log(err)
})
  • 这个AxiosResponse是固定格式类型,就像你用某个api,它会返回固定格式的东西一样,这个格式也不是你决定的,是这个api封装的时候决定的,这个格式在声明文件里写了。
  • 这个能成就说明原生axios调用ok,仿照这个调用方法写个自己的。
  • 先建个目录,里面放3个文件,axios.tsx放axios类,type.tsx放声明,index.tsx导出axios实例给外界调用。

axios/types.tsx

export type Methods =  'get' | 'GET'
| 'delete' | 'DELETE'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
| 'link' | 'LINK'
| 'unlink' | 'UNLINK'

//定义传入接口
export interface AxiosRequestConfig{
    url:string
    method:Methods
    params:any
}

//定义实例方法 
export interface AxiosInstance{
    <T=any>(config:AxiosRequestConfig):Promise<AxiosResponse>
}

//定义返回接口
export interface AxiosResponse<T = any>  {
    data: T;
    status: number;
    statusText: string;
    headers?: Record<string,any>;
    config?: AxiosRequestConfig;
    request?: XMLHttpRequest;
}
  • 这个没什么说的,就是我要传入什么,返回什么,写出来即可。

axios/index.tsx

import Axios from './axios'
import {AxiosInstance}from './types'
function creatInstance():AxiosInstance{
    let context:Axios = new Axios();
    //相当于执行instance就执行Axios原型上request方法,指针是Axios实例
    let instance :AxiosInstance = Axios.prototype.request.bind(context)
    //把实例,原型,方法合并,可以拿到所有身上的方法
    instance =Object.assign(instance,Axios.prototype,context)
    return instance as AxiosInstance
}

let axios:AxiosInstance = creatInstance()
export default axios
export * from './types'
  • 这里有个很骚的操作,弄了个实例后,拿去绑到类的方法上去,也变相相当于每次在实例上写了个方法,至于为啥这么做?
  • 首先,我要保证每个axios都是不同的玩意,有人说我写个function(类),然后把配置项传进去不也行?其实这是不行的,因为你所有地方调的都是同一个方法,同步还好说,如果异步的话,那岂不是乱套了。
  • 第二、如果是实例的话,它是个对象啊,它怎么才能像调用方法一样把一个配置对象传到另一个对象里?有人说Object.defineProperty?那个不行,只能让你变成实例点方法进行调用。所以源码就用上这个骚操作,把类里的方法绑到实例上套层函数,顺便把this指向改了,避免全是同样的this。有人会想,可以拿个函数包装一下,利用call函数,然后传入的配置项给类上的方法,其实跟这个原理差不多,跟这个没本质区别。
  • 最后一个合并什么意思?就是把这个类原型上可枚举的方法加到自己身上,这样在原型上加方法每个实例上也有,至于为啥把实例上的方法加过来?我也没懂,有懂的说一下。源码是通过for循环一个个加上去的,实例也加的。

axios/axios.tsx

import {AxiosRequestConfig} from './types'
export default class Axios{
    request<T>(config:AxiosRequestConfig):Promise<T>{
        return this.dispatchRequest(config)
    }
    dispatchRequest<T>(config:AxiosRequestConfig):Promise<T>{
        return new Promise<T>((resolve,reject)=>{
        })
    }
}
  • 这个类没啥说的,基本骨架是这样。
  • 下面在类里用promise包装ajax:
import {AxiosRequestConfig,AxiosResponse} from './types'
import qs from 'qs'
import parseHeaders from 'parse-headers'
export default class Axios{
    request(config:AxiosRequestConfig):Promise<AxiosResponse>{
        return this.dispatchRequest(config)
    }
    dispatchRequest(config:AxiosRequestConfig):Promise<AxiosResponse>{
        return new Promise<AxiosResponse>((resolve,reject)=>{
            let {method,url,params} = config
            let request = new XMLHttpRequest();
            if(params&&typeof params==='object'){
                params=qs.stringify(params)           
            }
            url += (url.includes('?')?'&':'?'+params)
            request.open(method,url,true)
            request.responseType='json'
            request.onreadystatechange=function(){
                //4完成
                if(request.readyState===4){//
                    if(request.status>=200&&request.status<300){
                        let response:AxiosResponse={
                            data:request.response?request.response:request.responseText,
                            status:request.status,
                            statusText:request.statusText,
                            //响应头xxx=xxx转换成对象格式
                            headers:parseHeaders(request.getAllResponseHeaders()),
                            config,
                            request
                        }
                        resolve(response)
                    }else{
                        reject('请求失败')
                    }
                }
            }
            request.send()
        })
    }
}
  • 这个没啥说的,然后对传入类型进行限定,并且把方法改成post,写post方法

index.tsx

import axios,{AxiosResponse} from './axios';
const baseURL = "http://localhost:8080";
interface User{
    name:string
    password: string
}
let user : User ={
    name:'yehuo',
    password:'123456'
}
axios({
    method:'post',
    url:baseURL+'/post',
    data:user,
    headers:{
        'Content-Type':'application/json'
    }
}).then((response:AxiosResponse<User>)=>{
    console.log(response);
    return response.data
}).catch(err=>{
    console.log(err)
})

axios/types.tsx

export type Methods =  'get' | 'GET'
| 'delete' | 'DELETE'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
| 'link' | 'LINK'
| 'unlink' | 'UNLINK'

//定义传入接口
export interface AxiosRequestConfig{
    url:string
    method:Methods
    params?:any,
    data?:Record<string,any>,
    headers?:Record<string,any>
}

//定义实例
export interface AxiosInstance{
    <T=any>(config:AxiosRequestConfig):Promise<AxiosResponse<T>>
}

//定义返回接口
export interface AxiosResponse<T=any>  {
    data: T;
    status: number;
    statusText: string;
    headers?: Record<string,any>;
    config?: AxiosRequestConfig;
    request?: XMLHttpRequest;
}

axios/index.tsx

import Axios from './axios'
import {AxiosInstance}from './types'
function creatInstance<T>():AxiosInstance{
    let context:Axios<T> = new Axios();
    //相当于执行instance就执行Axios原型上request方法,指针是Axios实例
    let instance :AxiosInstance = Axios.prototype.request.bind(context)
    //把实例,原型,方法合并,可以拿到所有身上的方法
    instance =Object.assign(instance,Axios.prototype,context)
    return instance as AxiosInstance
}

let axios:AxiosInstance = creatInstance()
export default axios
export * from './types'

axios/axios.tsx

import {AxiosRequestConfig,AxiosResponse} from './types'
import qs from 'qs'
import parseHeaders from 'parse-headers'
export default class Axios<T>{
    request(config:AxiosRequestConfig):Promise<AxiosResponse<T>>{
        return this.dispatchRequest(config)
    }
    dispatchRequest(config:AxiosRequestConfig):Promise<AxiosResponse<T>>{
        return new Promise<AxiosResponse<T>>((resolve,reject)=>{
            let {method,url,params,headers,data} = config
            let request = new XMLHttpRequest();
            let tmp:string|undefined;
            if(params&&typeof params==='object'){
                tmp = qs.stringify(params)           
            }
            url += (url.includes('?')?'&':'?'+(tmp?tmp:''))
            request.open(method,url,true)
            request.responseType='json'
            request.onreadystatechange=function(){
                //4完成
                if(request.readyState===4){//
                    if(request.status>=200&&request.status<300){
                        let response:AxiosResponse<T>={
                            data:request.response?request.response:request.responseText,
                            status:request.status,
                            statusText:request.statusText,
                            //响应头xxx=xxx转换成对象格式
                            headers:parseHeaders(request.getAllResponseHeaders()),
                            config,
                            request
                        }
                        resolve(response)
                    }else{
                        reject('请求失败')
                    }
                }
            }
            if(headers){
                for(let key in headers){
                    request.setRequestHeader(key,headers[key])
                }
            }
            let body:string|null = null
            if(data){
                body=JSON.stringify(data)
            }
            request.send(body)
        })
    }
}
  • 这个params和data分别对应不同发送方式,原版就是这样,如果只写params去发post是收不到data的。

二、错误处理

  • 错误处理分为网络异常,超时异常,错误状态码。

  • 网络异常只要配置request.onerror即可。

  • 超时异常需要配置timeout,定义好类型后,给request配置ontimeout和timeout。另外超时错误码是0,把前面判断条件排除一下。

  • 状态码错误就把请求错误那换一下即可。

  • axios/types.tsx加上声明

export interface AxiosRequestConfig{
    url:string
    method:Methods
    params?:any,
    data?:Record<string,any>,
    headers?:Record<string,any>,
    timeout?:number
}

axios/axios.tsx

import {AxiosRequestConfig,AxiosResponse} from './types'
import qs from 'qs'
import parseHeaders from 'parse-headers'
export default class Axios<T>{
    request(config:AxiosRequestConfig):Promise<AxiosResponse<T>>{
        return this.dispatchRequest(config)
    }
    dispatchRequest(config:AxiosRequestConfig):Promise<AxiosResponse<T>>{
        return new Promise<AxiosResponse<T>>((resolve,reject)=>{
            let {method,url,params,headers,data,timeout} = config
            let request = new XMLHttpRequest();
            let tmp:string|undefined;
            if(params&&typeof params==='object'){
                tmp = qs.stringify(params)           
            }
            url += (url.includes('?')?'&':'?'+(tmp?tmp:''))
            request.open(method,url,true)
            request.responseType='json'
            request.onreadystatechange=function(){
                //4完成
                if(request.readyState===4&&request.status!==0){
                    if(request.status>=200&&request.status<300){
                        let response:AxiosResponse<T>={
                            data:request.response?request.response:request.responseText,
                            status:request.status,
                            statusText:request.statusText,
                            //响应头xxx=xxx转换成对象格式
                            headers:parseHeaders(request.getAllResponseHeaders()),
                            config,
                            request
                        }
                        resolve(response)
                    }else{
                        reject(`超时错误码${request.status}`)
                    }
                }
            }
            if(headers){
                for(let key in headers){
                    request.setRequestHeader(key,headers[key])
                }
            }
            let body:string|null = null
            if(data){
                body=JSON.stringify(data)
            }
            request.onerror=function(){
                reject("网络不通")
            }
            if(timeout){
                request.timeout=timeout
                request.ontimeout=function(){
                    reject(`超过已配置时间${timeout}ms`)
                }
            }
            request.send(body)
        })
    }
}

三、实现拦截器功能

  • 先看下原版功能:
import axios,{AxiosResponse,AxiosRequestConfig} from 'axios';
const baseURL = "http://localhost:8080";
interface User{
    name:string
    password: string
}
let user : User ={
    name:'yehuo',
    password:'123456'
}
let request = axios.interceptors.request.use((config:AxiosRequestConfig):AxiosRequestConfig=>{
    config.headers.name+='1'
    return config
})
axios.interceptors.request.use((config:AxiosRequestConfig):AxiosRequestConfig=>{
    config.headers.name+='2'
    return config
})
axios.interceptors.response.use((response:AxiosResponse):AxiosResponse=>{
    response.data.name+='3'
    return response
})
let response = axios.interceptors.response.use((response:AxiosResponse):AxiosResponse=>{
    response.data.name+='4'
    return response
})
axios.interceptors.request.eject(request)
axios.interceptors.response.eject(response)

setTimeout(() => {
    axios({
        method:'post',
        url:baseURL+'/post',
        data:user,
        headers:{
            'Content-Type':'application/json',
            'name':'bbbb'
        },
        timeout:1000
    }).then((response:AxiosResponse<User>)=>{
        console.log(response);
        return response.data
    }).catch(err=>{
        console.log(err)
    })
}, 3000);
  • 其中use是可以添加拦截器,eject就是把拦截器删掉,打开网页可以验证一下。
  • 这个headers的键不能瞎填,浏览器和服务端不认。
  • 这个拦截器如果阻塞了,或者用promise异步then,都会等待走完,直到拿到返回值才继续走。
  • 拦截器请求是先加后执行,响应是先加先执行,就是往中间靠的三明治一样的感觉,有新的往外层加,中间核心是发送请求。
  • 如果拦截器报错,直接跳到axios的catch,请求也不会发。
  • 再来分析下怎么实现:
  • axios通过上面可以发现实际是个bind包的函数(这里叫实例有点误导性,源码是这么叫的),axios.intercepters就应该是配置在这个函数上的属性。可以通过intercepters点request或者点response说明这个结构应该是intercepters包着request和response,这个request和response里面还有2方法,添加就是use,删除就是eject。
  • intercepters是啥玩意?对象有可能吗?可能的,如果对象包着request和response是可以的。函数可能吗?也是有可能的,函数有这2属性即可。
  • 推一下request和response是啥玩意,request和response里面需要调添加和删除的方法,那么request和response里面应该要有个数组把拦截器存起来。每一个axios是bind包的函数,指向axios不同实例,那么这个request和response会跟着intercepters一起分给每个axios实例,所以这里的request和response如果写成函数,那么所有的request会共有一个数组,所有的response也会共有一个数组。所以这里request和response必然为类的实例。
  • 最后推一下use和eject大概啥玩意,use里面有个2个回调函数,一个是成功的回调,一个是失败的回调,都是用户传的,然后这个use完有个返回值,eject通过拿返回值把数组里的回调删了。那么这个返回值比较容易想的就应该是这个数组的索引。use时候把成功和失败回调包成一个整体push进数组,这样索引就能确定。
  • 基本结构理清,下面放代码:

axios/axios.tsx

import {AxiosRequestConfig,AxiosResponse} from './types'
import qs from 'qs'
import parseHeaders from 'parse-headers'
import AxiosInterceptorManager ,{Interceptor} from './axiosinterceptor';
export default class Axios<T>{
    interceptors = {
        request:new AxiosInterceptorManager<AxiosRequestConfig>(),
        response:new AxiosInterceptorManager<AxiosResponse<T>>()
    }
    request(config:AxiosRequestConfig):Promise<AxiosResponse<T>|AxiosRequestConfig>{
        let chain :any[]=[
            {
                onFulfilled:this.dispatchRequest,
                onRejected:(err:any)=>err
            }
        ]
        this.interceptors.request.interceptors.forEach((interceptor:Interceptor<AxiosRequestConfig>|null)=>{
            interceptor&&chain.unshift(interceptor)
        })
        this.interceptors.response.interceptors.forEach((interceptor:Interceptor<AxiosResponse<T>>|null)=>{
            interceptor&&chain.push(interceptor)
        })
        let promise:Promise<AxiosRequestConfig|AxiosResponse<T>>=Promise.resolve(config)
        while(chain.length){
            const {onFulfilled,onRejected}=chain.shift()
            promise = promise.then(onFulfilled,onRejected)
        }
        return promise
    }
    dispatchRequest(config:AxiosRequestConfig):Promise<AxiosResponse<T>>{
        return new Promise<AxiosResponse<T>>((resolve,reject)=>{
            let {method,url,params,headers,data,timeout} = config
            let request = new XMLHttpRequest();
            let tmp:string|undefined;
            if(params&&typeof params==='object'){
                tmp = qs.stringify(params)
            }
            url += (url.includes('?')?'&':'?'+(tmp?tmp:''))
            request.open(method,url,true)
            request.responseType='json'
            request.onreadystatechange=function(){
                //4完成
                if(request.readyState===4&&request.status!==0){
                    if(request.status>=200&&request.status<300){
                        let response:AxiosResponse<T>={
                            data:request.response?request.response:request.responseText,
                            status:request.status,
                            statusText:request.statusText,
                            //响应头xxx=xxx转换成对象格式
                            headers:parseHeaders(request.getAllResponseHeaders()),
                            config,
                            request
                        }
                        resolve(response)
                    }else{
                        reject(`超时错误码${request.status}`)
                    }
                }
            }
            if(headers){
                for(let key in headers){
                    request.setRequestHeader(key,headers[key])
                }
            }
            let body:string|null = null
            if(data){
                body=JSON.stringify(data)
            }
            request.onerror=function(){
                reject("网络不通")
            }
            if(timeout){
                request.timeout=timeout
                request.ontimeout=function(){
                    reject(`超过已配置时间${timeout}ms`)
                }
            }
            request.send(body)
        })
    }
}

axios/axiosinterceptor.tsx

interface OnFulfilled<T>{
    (value:T):T|Promise<T>
}
interface OnRejected{
    (err:any):any
}
export interface Interceptor<T>{
    onFulfilled?:OnFulfilled<T>
    onRejected?:OnRejected
}
export default class  AxiosInterceptorManager<T>{
      interceptors:Array<Interceptor<T>|null>=[]
      use(onFulfilled:OnFulfilled<T>,onRejected?:OnRejected):number{
        this.interceptors.push({
            onFulfilled,
            onRejected
        })
        return this.interceptors.length-1
      }
      eject(id:number){
          if(this.interceptors[id]){
              this.interceptors[id]=null
          }
      }
}

axios/types.tsx

import AxiosInterceptorManager from './axiosinterceptor'
export type Methods =  'get' | 'GET'
| 'delete' | 'DELETE'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
| 'link' | 'LINK'
| 'unlink' | 'UNLINK'

//定义传入接口 
export interface AxiosRequestConfig{
    url:string
    method:Methods
    params?:any,
    data?:Record<string,any>,
    headers?:Record<string,any>,
    timeout?:number
}

  
//定义实例
export interface AxiosInstance{
    <T=any>(config:AxiosRequestConfig):Promise<AxiosResponse<T>>
    interceptors:{
        request:AxiosInterceptorManager<AxiosRequestConfig>
        response:AxiosInterceptorManager<AxiosResponse >
    }
}

//定义返回接口
export interface AxiosResponse<T=any>  {
    data: T;
    status: number;
    statusText: string;
    headers?: Record<string,any>;
    config?: AxiosRequestConfig;
    request?: XMLHttpRequest;
}

axios/index.tsx

import Axios from './axios'
import {AxiosInstance}from './types'

function creatInstance<T>():AxiosInstance{
    let context:Axios<T> = new Axios();
    //相当于执行instance就执行Axios原型上request方法,指针是Axios实例
    let instance = Axios.prototype.request.bind(context)
    //把实例,原型,方法合并,可以拿到所有身上的方法
    instance =Object.assign(instance,Axios.prototype,context)
    return instance as AxiosInstance
}

let axios:AxiosInstance = creatInstance()
export default axios
export * from './types'
  • 可以验证下拦截器是否有效,我已经试过ok。
  • 里面还有个思路值得学习下,先做个promise传入配置项,这样后续的then的回调函数会把配置项给接上,从数组中shift回调函数,用while不停then。

四、实现默认配置功能

  • 默认配置就是设置一遍,不配对应选项默认就是这个配置。
  • 原版调用是用axios.defaults
  • 原理很简单,就是用default配置项去合并用户传入的配置项。不同之处在于请求头有些方法:
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
  • 也就是需要把方法给替换掉,如果key里有这些方法,那么就把这个方法里面的key给加到请求头上。
let defaults :AxiosRequestConfig={
    method:'get',
    timeout:0,
    headers:{
        common:{
            'accept':'application/json',
            'name':'34'
        }
    }
}
let getStyleMethod =['get','head','delete','option']
let postStyleMethod=['put','post','patch']
getStyleMethod.forEach((method)=>{
    defaults.headers![method]={}
})
postStyleMethod.forEach((method)=>{
    defaults.headers![method]={
        'Content-Type':'application/json',
    }
})
let allMethod = [...getStyleMethod,...postStyleMethod]
  • 在axios类里定义default:
  defaults:AxiosRequestConfig =defaults
  • 类里的request方法里合并请求头
   config.headers=Object.assign(this.defaults.headers,config.headers)
  • 在dispatchRequest方法里把headers判断加上
if(headers){
	  for(let key in headers){
	         if(key==='common'||allMethod.includes(key)){//排除自定方法
	             if(key==='common'||key===config.method){//本次发送方法
	                 for(let key2 in headers[key]){           
	                     request.setRequestHeader(key2,headers[key][key2])
	                 }
	             }         
	         }else{
	             request.setRequestHeader(key,headers[key])//正常请求头
	         }
	   }
}
  • types.tsx里把类型加上
export interface AxiosInstance{
    <T=any>(config:AxiosRequestConfig):Promise<AxiosResponse<T>>
    interceptors:{
        request:AxiosInterceptorManager<AxiosRequestConfig>
        response:AxiosInterceptorManager<AxiosResponse >
    },
    defaults:Required<AxiosRequestConfig>
}
  • 最后拿例子试验下,就完成了。
axios.defaults.headers.post['name']='324'
axios.defaults.headers.common['name']='213123'

五、实现转换请求响应

  • 先看一下原版这玩意干啥的:
  // `transformRequest` allows changes to the request data before it is sent to the server
  // This is only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'
  // The last function in the array must return a string or an instance of Buffer, ArrayBuffer,
  // FormData or Stream
  // You may modify the headers object.
  transformRequest: [function (data, headers) {
    // Do whatever you want to transform the data

    return data;
  }],

  // `transformResponse` allows changes to the response data to be made before
  // it is passed to then/catch
  transformResponse: [function (data) {
    // Do whatever you want to transform the data

    return data;
  }],
  • 这个东西是在用户传入参数里可以设置的,然后自己写个回调函数。
  • 比如:
axios({
    method:'post',
    url:baseURL+'/post',
    data:user,
    headers:{
        // 'Content-Type':'application/json',
        // 'name':'bbbb'
    },
    timeout:1000,
    transformRequest:(data,head)=>{     
        head['common']['name']='kcvxcvc'
        return data
    },
    transformResponse:(response)=>{
        console.log(response);      
        return response.data
    }
}).then((response:AxiosResponse<User>)=>{
    console.log(response);
    return response.data
}).catch(err=>{
    console.log(err)
})
  • 原理大家肯定都猜到了,就是在路上设置个回调,如果有就进入回调,没有就不管。
  • types.tsx加上类型
export interface AxiosRequestConfig{
    url?:string
    method?:Methods
    params?:any,
    data?:Record<string,any>,
    headers?:Record<string,any>,
    timeout?:number,
    transformRequest?:(data:any,headers:any)=>any,
    transformResponse?:(data:any)=>any
}
  • request里加上回调,注意要在合并默认请求头之后,这样headers会拿到默认的配置,不然undefined
   if(config.transformRequest&&config.data){      
            config.data=config.transformRequest(config.data,config.headers)    
        }
  • dispatchRequest里加入响应回调,就是在resolve请求前
   if(config.transformResponse){
        response=config.transformResponse(response)
    }
  • 这样就完成了。

六、实现任务取消

  • 任务取消用的还是比较多的,就是cancelToken。
  • 先看用法:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
  • 先理一下结构,axios里有个canceltoken的属性,这个属性里面有个source方法,执行后的返回值有个token属性,配置到配置项里,返回值还有个cancel方法,执行后会取消。
  • 浏览器会显示source.cancel后面的字符串,可以发现是个Cancel类做的实例,里面有个message属性。而且这个log是catch里面打印出来的,也就是最后外层走promise的reject的。
  • 首先想一下,每个axios是独立的,那么这个axios.CancelToken应该是在Axios类里的方法,里面source执行后有2返回值,而我们发现最后浏览器有个Cancel类,那么就是source的返回值里的cancel执行会new一个Cancel的实例出来。这个Cancel类也很简单,传个message即可,另外他还需要包进promise里。然后是另一个source返回值token,token最终是配置在配置项里的,如果有token那么发送时怎么工作呢?应该是通过token找到并执行和token一起配置的cancel,那么token如何才能执行到cancel?显然,需要个公有变量放到CancelToken里,结合前面cancel是包在promise里的,那么这个公有变量就应该是resolve,cancel存到resolve里,等用户调用直接改变promise状态,那么token就是个promise,如果token存在,就在这个promise处理完resolve后关闭请求(then),把外层大promise变成失败态走rejecet,这个resolve如果一直不执行,自然就一直不会触发后面的then,而执行就交给了cancel,只要用户调用cancel,就立马触发resolve。
  • 这样思路就理清了,axios/cancel写下两个类
export class Cancel{
    message:string;
    constructor(message:string){
        this.message=message
    }
}
export class CancelToken{
    resolve:any;
    source(){
        return{
            token:new Promise((resolve)=>{
                this.resolve=resolve
            }),
            cancel:(message:string)=>{
                this.resolve(new Cancel(message))
            },
        }
    }
}
  • axios/types修改声明
export interface AxiosInstance{
    <T=any>(config:AxiosRequestConfig):Promise<AxiosResponse<T>>
    interceptors:{
        request:AxiosInterceptorManager<AxiosRequestConfig>
        response:AxiosInterceptorManager<AxiosResponse >
    },
    defaults:Required<AxiosRequestConfig>,
    isCancel: (err:any)=>boolean,
    CancelToken:CancelToken
}
  • 把方法加到axios类上:
    CancelToken:CancelToken=new CancelToken()
    isCancel= (err:any)=>{
        return err instanceof Cancel
    }
  • 发送请求前判断token
     if(config.cancelToken){
                config.cancelToken.then((message:string)=>{
                    request.abort()
                    reject(message)
                })
            }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

业火之理

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值