依赖分析 1 2 "isomorphic-fetch" : "^2.2.1" , "qs" : "^6.9.1"
使用isomorphic-fetch,底层使用fetch请求
从特性到源码 request options定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 export interface RequestOptionsInit extends RequestInit { charset?: 'utf8' | 'gbk' ; requestType?: 'json' | 'form' ; data?: any ; params?: object | URLSearchParams ; paramsSerializer?: (params: object ) => string ; responseType?: ResponseType ; useCache?: boolean ; ttl?: number ; timeout?: number ; errorHandler?: (error: ResponseError ) => void ; prefix?: string ; suffix?: string ; throwErrIfParseFail?: boolean ; parseResponse?: boolean ; cancelToken?: CancelToken ; getResponse?: boolean ; validateCache?: (url: string , options: RequestOptionsInit ) => boolean ; __umiRequestCoreType__?: string ; }
extend 一般我们项目中的请求很多处理都一样,所以可以使用extend来定制配置,然后调用只需要直接调用请求即可。
创建一个request实例 1 export const extend = initOptions => request (initOptions);
request实例 创建实例 1 2 3 4 5 6 7 8 9 const request = (initOptions = {} ) => { const coreInstance = new Core (initOptions); const umiInstance = (url, options = {} ) => { const mergeOptions = mergeRequestOptions (coreInstance.initOptions , options); return coreInstance.request (url, mergeOptions); }; ... return umiInstance; };
.get、.post方法的语法糖 1 2 3 4 const METHODS = ['get' , 'post' , 'delete' , 'put' , 'patch' , 'head' , 'options' , 'rpc' ];METHODS .forEach (method => { umiInstance[method] = (url, options ) => umiInstance (url, { ...options, method }); });
拓展实例 1 export const extend = initOptions => request (initOptions);
Core对象 构造函数 1 2 3 4 5 6 7 8 constructor (initOptions ) { this .onion = new Onion ([]); this .fetchIndex = 0 ; this .mapCache = new MapCache (initOptions); this .initOptions = initOptions; this .instanceRequestInterceptors = []; this .instanceResponseInterceptors = []; }
request方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 request (url, options ) { const { onion } = this ; const obj = { req : { url, options }, res : null , cache : this .mapCache , responseInterceptors : [...Core .responseInterceptors , ...this .instanceResponseInterceptors ], }; if (typeof url !== 'string' ) { throw new Error ('url MUST be a string' ); } return new Promise ((resolve, reject ) => { this .dealRequestInterceptors (obj) .then (() => onion.execute (obj)) .then (() => { resolve (obj.res ); }) .catch (error => { const { errorHandler } = obj.req .options ; if (errorHandler) { try { const data = errorHandler (error); resolve (data); } catch (e) { reject (e); } } else { reject (error); } }); }); }
拦截器 全局拦截器和实例拦截器 1 2 3 4 5 6 7 8 9 class Core { constructor (initOptions ) { this .instanceRequestInterceptors = []; this .instanceResponseInterceptors = []; } static requestInterceptors = [addfixInterceptor]; static responseInterceptors = []; }
注册拦截器 1 2 3 4 5 6 7 8 static requestUse (handler, opt = { global : true } ) { if (typeof handler !== 'function' ) throw new TypeError ('Interceptor must be function!' ); if (opt.global ) { Core .requestInterceptors .push (handler); } else { this .instanceRequestInterceptors .push (handler); } }
请求拦截 1 2 3 4 5 6 7 8 9 10 11 12 13 14 dealRequestInterceptors (ctx ) { const reducer = (p1, p2 ) => p1.then ((ret = {} ) => { ctx.req .url = ret.url || ctx.req .url ; ctx.req .options = ret.options || ctx.req .options ; return p2 (ctx.req .url , ctx.req .options ); }); const allInterceptors = [...Core .requestInterceptors , ...this .instanceRequestInterceptors ]; return allInterceptors.reduce (reducer, Promise .resolve ()).then ((ret = {} ) => { ctx.req .url = ret.url || ctx.req .url ; ctx.req .options = ret.options || ctx.req .options ; return Promise .resolve (); }); }
值得注意的是,其中的reducer的用法相当于
1 2 3 4 Promise .resolve ().then (() => console .log (1 )) .then (() => console .log (2 )) .then (() => console .log (3 ))
中间件 umi-request有
实例中间件
全局中间件,不同实例共享全局中间件
内核中间件,方便开发者拓展请求内核
中间件对象 1 2 3 4 5 6 7 8 9 10 11 class Onion { constructor (defaultMiddlewares ) { if (!Array .isArray (defaultMiddlewares)) throw new TypeError ('Default middlewares must be an array!' ); this .defaultMiddlewares = [...defaultMiddlewares]; this .middlewares = []; } static globalMiddlewares = []; static defaultGlobalMiddlewaresLength = 0 ; static coreMiddlewares = []; static defaultCoreMiddlewaresLength = 0 ; }
添加中间件 1 use (newMiddleware, opts = { global : false , core: false , defaultInstance: false } ) {}
通过global和core判断内核中间件和全局中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (global ) { Onion .globalMiddlewares .splice ( Onion .globalMiddlewares .length - Onion .defaultGlobalMiddlewaresLength , 0 , newMiddleware ); return ; } if (core) { Onion .coreMiddlewares .splice (Onion .coreMiddlewares .length - Onion .defaultCoreMiddlewaresLength , 0 , newMiddleware); return ; }
执行中间件 首先合并中间件
1 2 3 4 5 6 const fn = compose ([ ...this .middlewares , ...this .defaultMiddlewares , ...Onion .globalMiddlewares , ...Onion .coreMiddlewares , ]);
具体合并中间件逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export default function compose (middlewares ) { if (!Array .isArray (middlewares)) throw new TypeError ('Middlewares must be an array!' ); const middlewaresLen = middlewares.length ; for (let i = 0 ; i < middlewaresLen; i++) { if (typeof middlewares[i] !== 'function' ) { throw new TypeError ('Middleware must be componsed of function' ); } } return function wrapMiddlewares (params, next ) { let index = -1 ; function dispatch (i ) { if (i <= index) { return Promise .reject (new Error ('next() should not be called multiple times in one middleware!' )); } index = i; const fn = middlewares[i] || next; if (!fn) return Promise .resolve (); try { return Promise .resolve (fn (params, () => dispatch (i + 1 ))); } catch (err) { return Promise .reject (err); } } return dispatch (0 ); }; }
请求过程 默认中间件 umi-request的请求逻辑也是通过中间件实现的
1 2 3 const globalMiddlewares = [simplePost, simpleGet, parseResponseMiddleware];const coreMiddlewares = [fetchMiddleware];
simplePost中间件 对POST请求参数做处理,实现 query 简化、 post 简化
simpleGet中间件 对GET请求参数做处理,实现 query 简化、 post 简化
fetch请求中间件 处理请求的中间件
取消请求的实现 如何取消请求 umi-request中取消请求 你可以通过CancelToken.source来创建一个 cancel token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import Request from 'umi-request' ;const CancelToken = Request .CancelToken ;const { token, cancel } = CancelToken .source ();Request .get ('/api/cancel' , { cancelToken : token }).catch (function (thrown ) { if (Request .isCancel (thrown)) { console .log ('Request canceled' , thrown.message ); } else { } }); Request .post ('/api/cancel' , { name : 'hello world' }, { cancelToken : token }) cancel ('Operation canceled by the user.' );
CancelToken 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function CancelToken (executor ) { if (typeof executor !== 'function' ) { throw new TypeError ('executor must be a function.' ); } var resolvePromise; this .promise = new Promise (function promiseExecutor (resolve ) { resolvePromise = resolve; }); var token = this ; executor (function cancel (message ) { if (token.reason ) { return ; } token.reason = new Cancel (message); resolvePromise (token.reason ); }); } CancelToken .source = function source ( ) { var cancel; var token = new CancelToken (function executor (c ) { cancel = c; }); return { token : token, cancel : cancel, }; };
fetch中间件取消请求 1 response = Promise .race ([cancel2Throw (options, ctx), adapter (url, options)]);
使用Promise.race,当一个Promise完成,就不在等待别的Promisecancel2Throw方式是用来取消请求的
1 2 3 4 5 6 7 8 9 export function cancel2Throw (opt ) { return new Promise ((_, reject ) => { if (opt.cancelToken ) { opt.cancelToken .promise .then (cancel => { reject (cancel); }); } }); }
超时取消 超时取消也是使用了Promise.race
1 2 3 4 5 6 7 8 9 response = Promise .race ([cancel2Throw (options, ctx), adapter (url, options), timeout2Throw (timeout, ctx.req )]); function timeout2Throw (msec, request ) { return new Promise ((_, reject ) => { setTimeout (() => { reject (new RequestError (`timeout of ${msec} ms exceeded` , request, 'Timeout' )); }, msec); }); }