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

相關文章
相關標籤/搜索