开编
主要用于学习记录,写在这里抛砖引玉
axios和flyio都使用过一段时间,在这里说说fly的优点:
- 添加了锁概念,增加了对请求队列的处理
- 更加轻量
- 分层设计出来的Engine配合自定义adapter带来更好的复用和更多的玩法
源码探究
adapter
抽象出来的adapter模块,让我们只需要实现请求参数配置和响应数据返回,就可以带来跨端能力!!
拿微信小程序的adapter作例子。适配器需要返回一个函数,同时接受request请求参数和responseCallback回调函数。其他端的基本大同小异,根据request设置参数,请求结束后调用responseCallback。
Engine
Engine是XMLHttpRequest的抽象实现,和XMLHttpRequest保持统一接口
Engine和adapter形成了一个策略模式,Engine提供统一的接口供fly层调用,adapter负责请求响应的内核实现
open
open方法用来设置engine的method和url。
红色区域作用是判断url有没有带http字眼并且是否是浏览器环境,它会通过a标签的href补全url。然后触发_changeReadyState,修改readyState的值,并且通过 _call 触发 onreadystatechange 钩子
_call是专门用来触发钩子的方法,首先判断engine是否注册了该钩子,然后通过apply,将name之后的参数全部放置到里面
send
send用来启动adapter。会先判断是否有adapter,没有则会报错
这段代码做了3件事
- 整合参数request
- 判断请求方式是否是GET,是的话会清空body,所以我们要遵守好规范,要把GET参数放到URL上,而不是放到body上,不然会出现某些乌龙(虽然本质上那些请求方式都是一样的)
- 将readyState修改成3。触发onreadystatechange钩子
一个超时逻辑。timeout默认是0,不设置超时操作
当超时触发后,将readyState回归为0(未初始化),并且触发onloadend、ontimeout、onreadystatechange钩子
然后这里调用adapter,把参数,和回调函数传进去,回调函数接受一个adapter的响应参数。
getAndDelete是取值函数,取值后会销毁该key
if判断是否超时或者中断等原因导致readyState !== 3。符合的话,会清除掉超时定时器,防止它后续触发
剩下的逻辑,基本是将response值转换到engine身上,供fly层调用。最后调用_changeReadyState(4)和触发onload钩子
abort
终止请求,将readyState设置为为初始0,触发onreadystatechange,onerror,onloadend钩子。
fly
fly是一个应用层。该层包含了拦截、锁、整合参数、注册engine事件等操作,提供多种请求方式API和all、spread等语法糖。
engine
fly构造函数接受一个engine作为参数,默认接受XMLHttpRequest。
拦截
相信用过axios的同学,对拦截肯定不陌生,拦截把对接口的公共处理从中分离,从而获得一个更良好的http模块结构。
同样fly也具备这个功能,当我们interceptors.request.use(handler, onerror)的时候,实际上是把handler, onerror分别赋值给interceptors.request的this.handler和this.onerror,供后续调用。
锁
请求后端接口时,为了验证安全,很多时候我们需要在接口header上塞进一个token,但我们应该在什么时候去请求获取这个token呢?进入页面时?登陆时?如果只是普通的浏览页面,额外去调这个接口并不划算,有登陆操作的话,获取token就显得合理多了,但是在某些场景下,登陆是静默的,不需要用户额外的操作。所以我们需要在拦截中对token进行处理。请求接口时当判断到没有token,没有则调用获取token接口。但是如果把获取token写在拦截上,请求过程是异步的,我们不能很好的防止后续接口重复调用获取token接口,所以我们需要一把锁,把后续的接口一直lock住。等请求成功后,unlock释放队列。这样就可以很好地解决了重复请求问题
每个fly实例都有3把锁,分别挂载在interceptors.request、interceptors.response和实例。实例的锁调用的是interceptors.request锁
初始化的时候,通过wrap函数为interceptors.request和interceptors.response添加了锁。
内部定义的resolve和reject分别用于队列锁定和解锁。当调用lock的时候,会为interceptor.p赋值一个Promise的实例,后续的请求都会在interceptor.p链中添加then,等待调用unlock函数,触发resolve后,释放队列,内部调用_clear清除掉interceptor.p、resolve、reject。
request
request是fly的请求方法,每次调用都会去new一个engine。然后生成一个promise,return出去。
- 有时候传参习惯只传一个object的参数,所以这里做了一段参数兼容
- 因为promise有不同的实现规范,所以这里用isPromise来判断,是否支持catch,也就是我们平时用的Promise/A+规范
enqueueIfLocked用来判断是否被lock,被lock的话则在promise添加一个then,不是的话则直接调用callback
- requestInterceptor.p用来判断是否lock,然后回调函数前面部分主要是做配置的整合。
- 判断requestInterceptorHandler拦截,接受拦截返回的参数,如果拦截返回的不是promise,将会被Promise.resolve转成promise。
- 操作ret,判断d否和options的引用相等,如果一样的话走makeRequest,不是的话,则直接resolve,返回这个b给用户
makeRequest的前半段都是用来整合参数
这一段逻辑主要是用来配置url,url补全和baseUrl合并
前半端用来设置请求参数,**"GET", "HEAD", "DELETE", "OPTION"**这些的body将和params合并到url上
然后调用engine.open初始化请求
下面继续设置跨域withCredentials,超时,响应的数据格式等等操作
这里我们注册了engine的生命钩子,engine发送请求。这里的setTimeout降低engine的运行优先级来干什么呢?我想是为了给abort打断的一个时间,或者还有其他用处吧
当engine请求成功后,会触发onload钩子,返回响应数据,会挂载到engine.responseText上,这时候,我们仍然要对数据进行处理。并且通过对status进行判断,符合则调用onresult,有问题的话,就调用onerror。
其实onerror到后面也是要经过onresult,不过传的参数则变为-1
onresult这里会根据type决定调用的是resolve还是reject。这里的resolve和reject,是决定用户request走then还是catch。
同样调用enqueueIfLocked,只不过这次换成了基于返回拦截的promise锁,只会影响到返回队列,同样,请求锁和实例上的锁只能影响请求队列。
基于request包装成多种请求方式
all&spread
all和spread两个简单语法糖。spread通过apply把数组解开成一个一个参数
流程图
从请求到响应这整个过程中,虽然engine会触发很多生命钩子,但是fly并不是照单全收。它只注册了onload,onerror,ontimeout等生命钩子