阅读flyio源码,一个跨端的http请求库

1,478 阅读6分钟

开编

分层设计 主要用于学习记录,写在这里抛砖引玉

axios和flyio都使用过一段时间,在这里说说fly的优点:

  • 添加了概念,增加了对请求队列的处理
  • 更加轻量
  • 分层设计出来的Engine配合自定义adapter带来更好的复用和更多的玩法

源码探究

adapter

抽象出来的adapter模块,让我们只需要实现请求参数配置和响应数据返回,就可以带来跨端能力!!

拿微信小程序的adapter作例子。适配器需要返回一个函数,同时接受request请求参数和responseCallback回调函数。其他端的基本大同小异,根据request设置参数,请求结束后调用responseCallback微信小程序adapter

Engine

EngineXMLHttpRequest的抽象实现,和XMLHttpRequest保持统一接口

Engineadapter形成了一个策略模式,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。 wrap

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等生命钩子