接口定義
// ./src/type/index.ts
export interface AxiosRequestConfig {
url?: string
method?: Method
data?: any
params?: any
headers?: any
responseType?: XMLHttpRequestResponseType
timeout?: number
transformRequest?: AxiosTransformer | AxiosTransformer[]
transformResponse?: AxiosTransformer | AxiosTransformer[]
cancelToken?: CancelToken
[propName: string]: any
}
//...
export interface CancelToken {
promise: Promise<string>
reason?: string
}
export interface Canceler {
(message?: string): void
}
export interface CancelExecutor {
(cancel: Canceler): void
}
類實現
// ./src/cancel/CancelToken.ts
import { CancelExecutor } from "../types"
interface ResolvePromise {
(reason?: string): void
}
export default class CancelToken {
promise: Promise<string>
reason?: string
constructor(executor: CancelExecutor) {
let resolvePromise: ResolvePromise
this.promise = new Promise<string>(resolve => {
resolvePromise = resolve
})
executor(message => {
if (this.reason) {
return
}
this.reason = message
resolvePromise(this.reason)
})
}
}
使用
// ./src/core/xhr.ts
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from '../types'
import { parseHeaders } from '../helpers/headers'
import { createError } from '../helpers/error'
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve, reject) => {
const { data = null, url, method = 'get', headers, responseType, timeout, cancelToken } = config
//...
if (cancelToken) {
cancelToken.promise.then(reason => {
request.abort()
reject(reason)
})
}
request.send(data)
//...
})
}
擴展靜態接口
// ./src/type/index.ts
//...
export interface CancelTokenSource {
token: CancelToken
cancel: Canceler
}
export interface CancelTokenStatic {
new(executor: CancelExecutor): CancelToken
source(): CancelTokenSource
}
// ./src/cancel/CancelToken.ts
import { CancelExecutor, CancelTokenSource, Canceler } from "../types"
interface ResolvePromise {
(reason?: string): void
}
export default class CancelToken {
promise: Promise<string>
reason?: string
constructor(executor: CancelExecutor) {
let resolvePromise: ResolvePromise
this.promise = new Promise<string>(resolve => {
resolvePromise = resolve
})
executor(message => {
if (this.reason) {
return
}
this.reason = message
resolvePromise(this.reason)
})
}
static source(): CancelTokenSource {
let cancel!: Canceler
const token = new CancelToken(c => {
cancel = c
})
return {
cancel,
token
}
}
}
cancel類實現及axios擴展
接口定義
// ./src/type/index.ts
//...
export interface AxiosStatic extends AxiosInstance {
create(config?: AxiosRequestConfig): AxiosInstance
CancelToken: CancelTokenStatic
Cancel: CancelStatic
isCancel: (value: any) => boolean
}
//...
export interface Cancel {
message?: string
}
export interface CancelStatic {
new(message?: string): Cancel
}
Cancel類
// ./src/cancel/Cancel.ts
export default class Cancel {
message?: string
constructor(message?: string) {
this.message = message
}
}
export function isCancel(value: any): boolean {
return value instanceof Cancel
}
將之前的reason替換爲cancel
// ./src/type/index.ts
export interface CancelToken {
promise: Promise<Cancel>
reason?: Cancel
}
// ./src/cancel/CancelToken.ts
import { CancelExecutor, CancelTokenSource, Canceler } from "../types"
import Cancel from "./Cancel"
interface ResolvePromise {
(reason?: Cancel): void
}
export default class CancelToken {
promise: Promise<Cancel>
reason?: Cancel
constructor(executor: CancelExecutor) {
let resolvePromise: ResolvePromise
this.promise = new Promise<Cancel>(resolve => {
resolvePromise = resolve
})
executor(message => {
if (this.reason) {
return
}
this.reason = new Cancel(message)
resolvePromise(this.reason)
})
}
static source(): CancelTokenSource {
let cancel!: Canceler
const token = new CancelToken(c => {
cancel = c
})
return {
cancel,
token
}
}
}
擴展axios的方法
// ./src/index.ts
import { AxiosRequestConfig, AxiosStatic } from './types'
import Axios from './core/Axios'
import { extend } from './helpers/util'
import defaults from './default'
import mergeConfig from './core/mergeConfig'
import CancelToken from './cancel/cancelToken'
import Cancel, { isCancel } from './cancel/Cancel'
function createInstance(config: AxiosRequestConfig): AxiosStatic {
const context = new Axios(config)
const instance = Axios.prototype.request.bind(context)
extend(instance, context)
return instance as AxiosStatic
}
const axios = createInstance(defaults)
axios.create = function create(config) {
return createInstance(mergeConfig(defaults, config))
}
axios.CancelToken = CancelToken
axios.Cancel = Cancel
axios.isCancel = isCancel
export default axios
一些其他的補充
// ./src/type/index.ts
export interface CancelToken {
promise: Promise<Cancel>
reason?: Cancel
throwIfRequested(): void
}
// ./src/cancel/CancelToken.ts
import { CancelExecutor, CancelTokenSource, Canceler } from "../types"
import Cancel from "./Cancel"
interface ResolvePromise {
(reason?: Cancel): void
}
export default class CancelToken {
//...
throwIfRequested() {
if (this.reason) {
throw this.reason
}
}
//...
}
// ./src/core/dispatchRequest.ts
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from '../types'
import xhr from './xhr'
import { buildURL } from '../helpers/url'
import { flattenHeaders } from '../helpers/headers'
import transform from './transform'
export default function dispatchRequest(config: AxiosRequestConfig): AxiosPromise {
throwIfCancellationRequested(config)
processConfig(config)
return xhr(config).then(res => {
return transformResponseData(res)
})
}
//...
function throwIfCancellationRequested (config: AxiosRequestConfig): void {
if (config.cancelToken) {
config.cancelToken.throwIfRequested()
}
}
測試
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="/__build__/cancel.js"></script>
</body>
</html>
// ./examples/cancel/app.ts
import axios, { Canceler } from '../../src/index'
const CancelToken = axios.CancelToken
const source = CancelToken.source()
axios.get('/cancel/get', {
cancelToken: source.token
}).catch(e => {
if (axios.isCancel(e)) {
console.log(`Request canceled ${e.message}`)
}
})
setTimeout(() => {
source.cancel('Operation canceled by the user.')
axios.post('/cancel/post', { a: 1 }, {
cancelToken: source.token
}).catch(e => {
if (axios.isCancel(e)) {
console.log(e.message)
}
})
}, 100)
let cancel: Canceler
axios.get('/cancel/get', {
cancelToken: new CancelToken(c => {
cancel = c
})
}).catch(e => {
if (axios.isCancel(e)) {
console.log('Request canceled')
}
})
setTimeout(() => {
cancel()
}, 200)
// ./examples/server.js
const express = require('express')
const bodyParser = require('body-parser')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddelware = require('webpack-hot-middleware')
const WebpackConfig = require('./webpack.config')
const app = new express()
const compiler = webpack(WebpackConfig)
app.use(webpackDevMiddleware(compiler, {
publicPath: '/__build__/',
stats: {
colors: true,
chunks: false
}
}))
app.use(webpackHotMiddelware(compiler))
app.use(express.static(__dirname))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))
const router = express.Router()
//...
router.get('/cancel/get', (req, res) => {
setTimeout(() => {
res.json('hello')
}, 1000)
})
router.post('/cancel/post', (req, res) => {
setTimeout(() => {
res.json(req.body)
}, 1000)
})
app.use(router)
const port = process.env.PORT || 8080
module.exports = app.listen(port, () => {
console.log(`Serve listening on http://localhost:${port}, Ctrl + C to stop`)
})