UNPKG

yuumi-request

Version:

request queue for browser

191 lines (168 loc) 6.08 kB
import { Queue } from "./queue/queue" import { Job, ResolveFun, RejectFun } from "./queue/job" import { RequestMethod } from "./request/type" import { XHR, XHR_RequestOption } from "./request/xhr" import { paramStringify } from "./utils" export interface YuumiRequestInterface { baseURI: string headers: Record<string, string> concurrency: number timeout: number } export type YuumiRequestCtorOption = { [K in keyof YuumiRequestInterface]?: YuumiRequestInterface[K] extends Function ? never : YuumiRequestInterface[K] } & { paramStringify?: (value: any) => string xhr?: <T>(option: XHR_RequestOption) => Promise<T> } export type RequestOption = { path: string method: RequestMethod async?: boolean headers?: Record<string, string> params?: Record<string, string|number> data?: Record<string, any> cancelToken?: (cancel?: () => void) => void timeout?: number enforce?: "pre"|"normal" uploader?: { [key: string]: EventListener } } export type MethodRequestOptions = { [K in keyof RequestOption as Exclude<K, "path"|"method">]: RequestOption[K]; } export class YuumiRequestInterceptor { public requestInterceptors: [ResolveFun, RejectFun?][] = [] public responseInterceptors: [ResolveFun, RejectFun?][] = [] constructor() {} request(resolve:ResolveFun, reject?: RejectFun) { this.requestInterceptors.push([resolve, reject]) } response(resolve:ResolveFun, reject?: RejectFun) { this.responseInterceptors.push([resolve, reject]) } } export class YuumiRequest implements YuumiRequestInterface { readonly baseURI!: string readonly headers!: Record<string, string> readonly concurrency!: number readonly timeout!: number readonly paramStringify?: (value: any) => string readonly xhr!: <T>(option: XHR_RequestOption) => Promise<T> // 拦截器 readonly interceptor: YuumiRequestInterceptor = new YuumiRequestInterceptor() // 请求队列 readonly queue!: Queue constructor(option?: YuumiRequestCtorOption) { const _option = Object.assign({ baseURI: "", headers: {}, concurrency: 4, timeout: 0, xhr: (config: XHR_RequestOption) => new XHR(config).exec() }, option) this.baseURI = _option.baseURI this.headers = _option.headers this.concurrency = _option.concurrency this.timeout = _option.timeout this.paramStringify = _option.paramStringify this.xhr = _option.xhr this.queue = new Queue([], { concurrency: this.concurrency, autoStart: true }) } public request<T>(option: RequestOption): Promise<T> { let _url = `${this.baseURI}${option.path}` const query = this.paramStringify ? this.paramStringify(option.params) : paramStringify(option.params) if (query) { _url = /\?/.test(_url) ? `${_url}&${query}` : `${_url}?${query}` } const config: XHR_RequestOption = { url: _url, method: option.method, async: option.async, headers: Object.assign({}, this.headers, option.headers), data: option.data, encodeData: this.paramStringify ? this.paramStringify(option.data) : paramStringify(option.data), cancelToken: option.cancelToken, timeout: option.timeout || this.timeout, uploader: option.uploader } const job = new Job() job.addTask(() => Promise.resolve(config)) // request interceptors this.interceptor.requestInterceptors.forEach((item) => { job.addTask(...item) }) // request job.addTask((value: any) => { // 拼接拦截器添加的params if (value.params) { const _query = this.paramStringify ? this.paramStringify(value.params) : paramStringify(value.params); if (_query) { value.url = /\?/.test(_url) ? `${_url}&${_query}` : `${_url}?${_query}`; } } return this.xhr(value as XHR_RequestOption).finally(() => { job.dispatch("afterXHR") }) }) // response interceptors this.interceptor.responseInterceptors.forEach((item) => { job.addTask(...item) }) const result = new Promise<any>((resolve, reject) => { // 当job在queue中等待时的cancelToken, 取消job直接返回 if(typeof config.cancelToken === 'function') { config.cancelToken(() => { job.cancel() reject({ code: -1, message: "request:aborted" }) }) } job.addTask(resolve, reject) }) if (option.enforce === "pre") { this.queue.unshift(job) } else { this.queue.push(job) } return result } public get<T>(path: string, params?: { [key: string]: number|string }, options?: MethodRequestOptions) { const _options = Object.assign({}, options, { path: path, method: 'GET', params: Object.assign({}, options?.params, params) }) as RequestOption return <Promise<T>>this.request.call(this, _options) } public post<T>(path: string, data?: any, options?: MethodRequestOptions) { const _options = Object.assign({}, options, { path: path, method: 'POST', data: Object.assign({}, options?.data, data) }) as RequestOption return <Promise<T>>this.request.call(this, _options) } public patch<T>(path: string, data?: any, options?: MethodRequestOptions) { const _options = Object.assign({}, options, { path: path, method: 'PATCH', data: Object.assign({}, options?.data, data) }) as RequestOption return <Promise<T>>this.request.call(this, _options) } public put<T>(path: string, data?: any, options?: MethodRequestOptions) { const _options = Object.assign({}, options, { path: path, method: 'PUT', data: Object.assign({}, options?.data, data) }) as RequestOption return <Promise<T>>this.request.call(this, _options) } public delete<T>(path: string, data?: any, options?: MethodRequestOptions) { const _options = Object.assign({}, options, { path: path, method: 'DELETE', data: Object.assign({}, options?.data, data) }) as RequestOption return <Promise<T>>this.request.call(this, _options) } }