我正在参加「掘金·启航计划」
接前面的章节,我们完成了axios的基本功能实现,并在最后章节中,我们对一些常见的xhr错误进行了捕获处理。
接下来,我们来对捕获的错误信息进行增强,建立AxiosError类
- 为什么需要
AxiosError?- 可以获取到更详细的请求和响应信息,帮助我们更快速地排查和解决问题。
需求分析
处理AxiosError
我们可以参考源码中的定义
github.com/axios/axios…
- 错误信息 mssage
- 错误代码 code
- 当前的 request config
- request
- response
首先,我们定义一个接口
export interface AxiosError extends Error {
config: AxiosRequestConfig
code?: string
request?: any
response?: AxiosResponse
isAxiosError: boolean
}
实现AxiosError类
import { AxiosRequestConfig, AxiosResponse } from './../types/index'
export class AxiosError extends Error {
config: AxiosRequestConfig
code?: string
request?: any
response?: AxiosResponse
isAxiosError: boolean
constructor(
message: string,
config: AxiosRequestConfig,
code?: string | undefined,
request?: any,
response?: AxiosResponse
) {
super(message)
this.config = config
this.code = code
this.request = request
this.response = response
this.isAxiosError = true
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
Object.setPrototypeOf(this, AxiosError.prototype)
}
}
// 对外暴露接口
export function createError(
message: string,
config: AxiosRequestConfig,
code?: string | undefined,
request?: any,
response?: AxiosResponse
): AxiosError {
const error = new AxiosError(message, config, code, request, response)
return error
}
在原有的axios中导出类型定义
// axios.ts
import {AxiosRequestConfig} from './types'
import xhr from './xhr'
function axios(config:AxiosRequestConfig){
xhr(config)
}
export default axios
// index.ts
import axios from './axios'
export * from './types'
export default axios
对原有的error修改成axiosError
// xhr.ts
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve, reject) => {
// 省略代码...
request.ontimeout = function handleTimeout() {
reject(
createError(
`Timeout of ${timeout} ms exceeded`,
config,
'ECONNABORTED',
request
)
)
}
request.open(method.toUpperCase(), url!, true)
request.onreadystatechange = function handleLoad() {
// 省略代码...
request.onerror = function handleError() {
reject(createError('Network Error', config, undefined, request))
}
// ...
function handleResponse(response: AxiosResponse): void {
if (response.status >= 200 && response.status < 300) {
resolve(response)
} else {
reject(
createError(
`Request fail with status code ${response.status}`,
config,
undefined,
request,
response
)
)
}
}
})
}
通过实现AxiosError类,我们对原有的error做了增强,更方便去排查问题。
Axios API的实现
接下来,我们来看下Axios API的实现,我们从文档中得知,发送axios请求可以有几种形式:
// Send a POST request
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
// Send a GET request (default method)
axios('/user/12345');
// The Axios Instance
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
我们可以知道,axios库提供了多种接口方便用户发送请求,并且axios是一个混合类型
我们先来定义axios的类型
export interface Axios {
request(config: AxiosRequestConfig): AxiosPromise
get(url: string, config?: AxiosRequestConfig): AxiosPromise
delete(url: string, config?: AxiosRequestConfig): AxiosPromise
head(url: string, config?: AxiosRequestConfig): AxiosPromise
options(url: string, config?: AxiosRequestConfig): AxiosPromise
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
}
// 定义了axios instance对象,可以直接使用
// 重载,兼容第一个传 url 的情况
export interface AxiosInstance extends Axios {
(config: AxiosRequestConfig): AxiosPromise
(url: string, config?: AxiosRequestConfig): AxiosPromise
}
// 因为要适配上述第二种情况,Url改为可选
export interface AxiosRequestConfig {
url?: string
// ...
}
具体实现:
将原有的xhr逻辑移入core目录改命为dispatchRequest
对外暴露的多个接口相当于语法糖,本质还是调用request方法,我们将request分为传data和不传data两种形式,再对其进行一层封装。
// core/Axios.ts
export default class Axios {
request(url: any, config?: any): AxiosPromise {
// 如果传入的是字符串,就把它放到 config 的 url 属性上
// 如果传入的是对象,就直接赋值给 config
if (typeof url === 'string') {
if (!config) {
config = {}
}
config.url = url
} else {
config = url
}
return dispatchRequest(config)
}
get(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData('get', url, config)
}
delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData('delete', url, config)
}
head(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData('head', url, config)
}
options(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData('options', url, config)
}
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithData('post', url, data, config)
}
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithData('put', url, data, config)
}
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithData('patch', url, data, config)
}
_requestMethodWithoutData(method: Method, url: string, config?: AxiosRequestConfig) {
return this.request(
Object.assign(config || {}, {
method,
url
})
)
}
_requestMethodWithData(method: Method, url: string, data?: any, config?: AxiosRequestConfig) {
return this.request(
Object.assign(config || {}, {
method,
url,
data
})
)
}
}
至此,我们实现了Axios类,然后我们将其转成混合类型
对应库的源码:github.com/axios/axios…
// helpers/utils
export function extend<T, U>(to: T, from: U): T & U {
for (const key in from) {
;(to as T & U)[key] = from[key] as any
}
return to as T & U
}
// src/axios.ts
import { AxiosInstance } from './types'
import Axios from './core/axios'
import { extend } from './helpers/utils'
function createInstance(): AxiosInstance {
const context = new Axios()
// 创建instance 指向 Axios.prototype.request 方法,并绑定了上下文 context
const instance = Axios.prototype.request.bind(context)
// 通过 extend 方法把 context 中的原型方法和实例方法全部拷贝到 instance 上
extend(instance, context)
return instance as AxiosInstance
}
const axios = createInstance()
export default axios
通过上述的操作,当我们调用axios对象时,内部生成Axios实例,然后将request方法绑定到实例中,当直接调用axios方法,相当于调用request。
总结
通过上述章节,我们实现了axios的错误处理增强和Axios混合类型。接下来,我们将对response做更友好的优化处理和拦截器的实现。