import EventEmitter from './EventEmitter' import merge from 'ts-deepmerge' import { getSign } from './util' const DEFAULT_CONFIG: EventRequestConfig = { maxConcurrent: 10, timeout: 0, header: {}, dataType: 'json', baseUrl: '', } type HttpHeader = Record interface EventRequestConfig { maxConcurrent: number /** 过期时间(毫秒) */ timeout: number header: HttpHeader dataType: 'json' | '其他' baseUrl: string } interface HttpRequestConfig { url: string method: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT' | undefined data: Object | string header: HttpHeader dataType: 'json' | '其他' } interface RequestJob { config: HttpRequestConfig resolve(data: any): void reject(data: any): void response?: any } interface HttpRequestCallBack { success(data: HttpResponse): void fail(err: HttpRequestErr): void complete(): void } interface HttpRequestErr { /** 错误信息 */ errMsg: string /** 需要基础库: `2.24.0` * * errno 错误码,错误码的详细说明参考 [Errno错误码](https://developers.weixin.qq.com/miniprogram/dev/framework/usability/PublicErrno.html) */ errno: number } interface HttpResponse { timeout?: string data: T errMsg: string statusCode: number header: Record } type RequestInterceptorCallback = (config: HttpRequestConfig) => boolean type ResponseInterceptorCallback = ( config: HttpRequestConfig, response: HttpResponse, ) => Promise class EventRequest extends EventEmitter { private config: EventRequestConfig private queue: RequestJob[] = [] private runningTask: number = 0 private maxConcurrent = DEFAULT_CONFIG.maxConcurrent private requestInterceptor: RequestInterceptorCallback = () => true private responseInterceptor: ResponseInterceptorCallback = (_, rsp) => new Promise((resolve) => resolve(rsp.data)) constructor(_config?: Partial) { super() if (_config) { this.config = merge({}, DEFAULT_CONFIG, _config) } else { this.config = DEFAULT_CONFIG } this.maxConcurrent = this.config.maxConcurrent } private next(): void { const queue = this.queue if (!queue.length || this.runningTask >= this.maxConcurrent) return const entity = queue.shift()! const httpConfig = entity.config const { requestInterceptor, responseInterceptor } = this if (requestInterceptor.call(this, httpConfig) !== true) { let response: HttpResponse = { data: null, errMsg: `Request Interceptor: Request can\'t pass the Interceptor`, statusCode: 0, header: {}, } entity.reject(response) return } this.emit('request', httpConfig) this.runningTask = this.runningTask + 1 let timer = -1 let aborted: boolean = false let finished: boolean = false const callBack: HttpRequestCallBack = { success: (res: HttpResponse): void => { if (aborted) return finished = true timer && clearTimeout(timer) entity.response = res this.emit('success', httpConfig, res) responseInterceptor .call(this, httpConfig, res) .then((rsp) => { entity.resolve(rsp) }) .catch((err) => { entity.reject(err) }) }, fail: (err: HttpRequestErr): void => { if (aborted) return finished = true timer && clearTimeout(timer) entity.response = err this.emit('fail', httpConfig, err) entity.reject(err) }, complete: () => { if (aborted) return this.emit('complete', httpConfig, entity.response) this.next() this.runningTask = this.runningTask - 1 }, } const requestConfig = Object.assign(httpConfig, callBack) const task = wx.request(requestConfig) if (this.config.timeout > 0) { timer = setTimeout(() => { if (!finished) { aborted = true entity.reject({ timeout: '访问超时', }) task && task.abort() this.next() } }, this.config.timeout) } } request( method: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT', url: string, data: Object | string = '', header: HttpHeader = {}, dataType?: 'json' | '其他', ): Promise { const token = getSign() const language = 'zh-Hans' let authHeader = {} if (token != null) { authHeader = { Authorization: `Bearer ${token}`, '.AspNetCore.Culture': `c=${language}|uic=${language}`, Accept: 'application/json', } as Record } const config: HttpRequestConfig = { method, url: this.config.baseUrl + url, data, header: { ...header, ...this.config.header, ...authHeader }, dataType: dataType || this.config.dataType, } return new Promise((resolve, reject) => { const entity: RequestJob = { config, resolve, reject, response: null } this.queue.push(entity) this.next() }) } head(url: string, data?: Object | string, header?: HttpHeader, dataType?: 'json' | '其他') { return this.request('HEAD', url, data, header, dataType) } options(url: string, data?: Object | string, header?: HttpHeader, dataType?: 'json' | '其他') { return this.request('OPTIONS', url, data, header, dataType) } get(url: string, data?: Object | string, header?: HttpHeader, dataType?: 'json' | '其他') { return this.request('GET', url, data, header, dataType) } post(url: string, data?: Object | string, header?: HttpHeader, dataType?: 'json' | '其他') { return this.request('POST', url, data, header, dataType) } put(url: string, data?: Object | string, header?: HttpHeader, dataType?: 'json' | '其他') { return this.request('PUT', url, data, header, dataType) } ['delete']( url: string, data?: Object | string, header?: HttpHeader, dataType?: 'json' | '其他', ) { return this.request('DELETE', url, data, header, dataType) } trace(url: string, data?: Object | string, header?: HttpHeader, dataType?: 'json' | '其他') { return this.request('TRACE', url, data, header, dataType) } connect(url: string, data?: Object | string, header?: HttpHeader, dataType?: 'json' | '其他') { return this.request('CONNECT', url, data, header, dataType) } setRequestInterceptor(interceptor: (config: HttpRequestConfig) => boolean) { this.requestInterceptor = interceptor return this } setResponseInterceptor( interceptor: (config: HttpRequestConfig, response: HttpResponse) => Promise, ) { this.responseInterceptor = interceptor return this } clean(): void { this.queue = [] } } export default EventRequest