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

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

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

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

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

// 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();
复制代码

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

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

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

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

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

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

// 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 源码, 这里不予过多评论, 说不定别人是有缘由的.npm

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

因而就有了以下代码:

InterceptorChainActor 调度流程

这是 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 提供.

相关文章
相关标签/搜索