RetrofitJs - TypeScript实现的声明式HTTP客户端

由于文档已经在 github 里写好了, 这里并不是很想再写一次中文版文档, 本文将着重于解析工具的设计.( 好像看国人写的英文也是件蛮痛苦的事哇? 本人英文渣渣. )

实际上 Retrofit 是 Java 的一款基于 OkHttp 开发的, 类型安全的声明式HTTP客户端, 在 Java 用惯了这一类的工具, 自然也会想在其他地方使用类似的工具, 于是乎自己写了一款, 加个 Js 当作后缀就当项目名了, 即 RetrofitJs.

当然主要还是因为懒, 假如在项目的每个页面都要写一大串 client.get({ ...config })来发起请求, 一来感觉太乱, 毕竟每个页面都要写就感觉很难管理. 二来重复度高, 因为同一个接口调用两次就要写两次配置( 说实话直接封装成函数我也觉得有点拙 ), 接口并不能重用. 本着懒惰是第一生产力的原则就写了个工具来解决上述问题.

Talk is cheap, 先来看个 demo, 这是 TypeScript 下的代码:

// This is typescript demo, also javascript demo( it is if you remove all type define )

// In the first step, you must create your retrofit object. 
let client = Retrofit.getBuilder()
  .setConfig<RetrofitConfig>( { /** config, you can use retrofit config or axios config */ } )
  .addInterceptor( /** your interceptor */ )
  .setErrorHandler( /** define your error handler */ )
  .build();

// This is the part of define any interface what you need.
@HTTP( "/testing" )
@Headers( [ "Cache-Control: no-store" ] )
class TestingClient {
  
  @GET( "/demo1/:callByWho/:when" )
  public demo1( @Path( "callByWho" ) name: string, @Path( "when" ) time: number ): RetrofitPromise<string> & void {
  }
  
  @POST( "/demo2/:file" )
  public demo2( @Path( "file" ) file: string, @Header( "cookie" ) val: string, @Config localConfig: AxiosConfig ): RetrofitPromise<string> & void {
  }
}

// The final step, create your client.
export let testingClient = client.create( TestingClient );

// When you are calling this method, it is a http call actually. 
testingClient.demo1( "itfinally", Date.now() ).then( response => {
    // any code
} ).catch( reason => {
	// any code
} );

// And you can also get axios instance.
let axios: AxiosInstance = client.getEngine();
复制代码

上面的例子其实还少了个东西, 实际上接口是可以继承的. 例如这样:

class Parent {
  @GET( "/a1_testing/:a1" )
  public demo1(): RetrofitPromise<string> & void {
  }
}

@HTTP( "/test" )
class TestingClient extends Parent {
}
复制代码

基于 Axios 的当然是同时支持 browser/nodejs 啦.

不过需要注意的是, 项目核心功能依赖于 Proxy, 所以并不能在非原生支持 ES6 的环境下使用. 同时 decorator 特性仍然在 stage 2, 并且最重要的 parameter decorator 在 17 年尾才被人提出并加入讨论( 这些事件我都有在项目文档开头说明, 需要了解的可以移步到项目的README.md ), 所以这个工具目前只能在 TypeScript 环境中使用.( 不要怪我等你脱了裤才告诉你这个事 = =. )

在设计前其实看过 Axios 和 OkHttp 的使用文档, 当然是更偏向于 OkHttp 的方式, 尤其是拦截器方面的设计.

// Axios 使用方式
// Add a request interceptor
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
  }, function (error) {
    // Do something with response error
    return Promise.reject(error);
  });

// OkHttp 使用方式
class MyInterceptor implements Interceptor {
    public Response intercept(Interceptor.Chain chain) throws IOException {
        chain.proceed( chain.request() );
    }
}
复制代码

明显是 OkHttp 在同一个作用域中处理一切的方式优于 Axios 把 request/response 隔离成两个作用域的方式, 当然我没详细看过 Axios 源码, 这里不予过多评论, 说不定别人是有原因的.

不管怎样, 当时是查了很多关于 OkHttp 源码解析的文档和博客, 其实关键就是实现一个责任链, 并且每个节点都可以把 request 传递给下一个节点.

于是就有了如下代码:

这是 InterceptorChainActor 里 Chain 的一段代码, 也是整个工具运行的其中一个关键, 字段 interceptors 是当前剩余的拦截器, 而字段 stack 是当前的调用栈. 可以从图片中看出整个责任链是通过 interceptors-stack 两者来维护的, 同时把自身( 即 Chain )作为参数传入当前调用的拦截器, 从而确保拦截器在调用 chain.proceed( chain.request() ) 时, 调用流程会重入到当前的 Chain.

要注意的是, 这实际上是一个递归调用, 所以拦截器太多的话也会出现溢出, 当然这是极端情况了.

另外在调用一个被 RetrofitJs 代理的方法时, 实际的调用如下:

  1. 装饰器被激活, 收集所有不存在的元数据, 如果已经收集过( 比如第二次调用 )则直接返回
  2. 代理函数( 即 Proxy.apply )获取当前元数据, 并且不断遍历原型链获取构造器元数据( 也就是类的元数据 )并组合
  3. 检查元数据并且根据当前传入的 parameters 创建出 request 对象
  4. 把 request 对象传入拦截链并返回一个 Promise 对象

为啥不截图呢? 因为整个代码太多了, 好几份文件.

说实话, 这个工具相对于前端而言, 特性有点激进, 当然也解决一部分问题, 最直观的就是代码更简洁, 尤其是调用的时候给人一种本地函数调用的错觉( 有点像RPC? ).

都看到这里了, 不介意的话可以试试哇, 目前项目在 npm 提供.

转载于:https://juejin.im/post/5a8ef112f265da4e9e3072a8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值