在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一、在GitHub上创建一个代码仓库找到仓库地址:[email protected]: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 [email protected]: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: readyState:属性返回一个 XMLHttpRequest 代理当前所处的状态。一个 XHR 代理总是处于下列状态中的一个。 getAllResponseHeaders() :方法返回所有的响应头,以\r\n分割的字符串,或者 修改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+处理响应data8.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 |
请发表评论