論聲明式 HTTP Client 在前端的應用

喵, 忽然想發一話題, 正如標題所言, 想討論一下前端發展到如今, 在 ajax 異步請求的一些設計.javascript

一枚 javaer 在談論 javascript 的東西, 但願不會被打. = =前端


 ==|||  發了貼很久才發現名詞說錯了。 應該叫作 聲明式 Http 客戶端. 改了下標題.java


在編寫前端網頁時, 會常常用到用異步請求來知足各類需求。那麼咱們是怎麼作的?ios

其實先想一想咱們在獲取數據時, 真正想作的是什麼?git

可能僅僅是 調用api -> 傳參數 -> 獲取返回值, 而後繼續本地的流程.es6

這就是咱們僅僅關心的事.github


可是在十幾年前的恐龍時代, 全部的瀏覽器只提供一個 XMLHttpRequest 對象, 裏面含括了全部關於 Http 請求的設置.ajax

很豐富, 可是也很痛苦, 由於發起一個請求須要設置大量的參數, 寫一大坨無關痛癢的代碼, 只是是爲了發起請求而且獲取結果.spring

後來 JQuery 出現了( 原諒我沒玩過其餘好比 prototype 這種前端框架, 我是個職業後臺 ), $.ajax 簡化了好多參數,axios

可是時代也在進步, 這時候又出來問題, 簡單列幾個痛點:

  1. 怎麼處理回調地獄( callback hell )?
  2. 一個網頁可能存在多個異步請求, 怎麼管理請求, 避免散落一地?
  3. 我想在數據返回的時候作個攔截處理怎麼辦?
  4. 同理, 請求前想作攔截處理怎麼辦?
  5. 怎麼管理以上的邏輯?

後來近幾年 es6 遍地開花, 出現了 Promise / fetch 這種專治老司機各類不服的 API, 再有後者 es7 推出的 async/await 直接把回調地獄打進了歷史教科書, 可是後面四個問題依然存在.


而後我看到了 axios 框架, 看了一下 github 裏面的教程, 好好好, 不錯, 該有的都有, 可是對於我這種用慣響應式客戶端的挑剔鬼而言, 是否是能夠再作得更完全一些?

好比 java 的 retrofit, 以及 feign , 都是聲明式客戶端的教科書, 而我依然只想作一件事, 帶着參數調用 api -> 獲取參數, 完事.

最後還能有統一的異常處理派發, 不至於散落一地, 簡而言之, 集中式管理.


最後參考了 retrofit 項目, 寫了一個 retrofitjs 工具, 來看看這又是怎麼玩的( 不要臉的我照搬了 springMVC 的註解 (^○^) ):

首先是定義, 寫出你須要的接口. ( 這是 TypeScript demo, 謝謝 )

@ResponseBody()
@RequestMapping( "/user" )
class UserDetailClient {
    @Args(
        "createStartTime", "createEndingTime", "updateStartTime", "updateEndingTime",
        "status", "nickname", "account", "page", "row"
    )
    @PostMapping( "/query_by_multi_condition" )
    public queryByMultiCondition( createStartTime: number, createEndingTime: number,
                                  updateStartTime: number, updateEndingTime: number,
                                  status: number, nickname: string, account: string,
                                  page: number, row: number ): Promise<any> {
        return <any>null;
    }

    @Args(
        "createStartTime", "createEndingTime", "updateStartTime", "updateEndingTime",
        "status", "nickname", "account"
    )
    @PostMapping( "/count_by_multi_condition" )
    public countByMultiCondition( createStartTime: number, createEndingTime: number,
                                  updateStartTime: number, updateEndingTime: number,
                                  status: number, nickname: string, account: string ): Promise<any> {
        return <any>null;
    }

    @Args( "id", "status", "name" )
    @PostMapping( "/update_user_detail" )
    public updateUserDetail( id: string, status: number, name: string ): Promise<any> {
        return <any>null;
    }
}複製代碼

而後初始化一個客戶端:

let retrofit = new FetchRetrofit.Builder()
    .baseUrl( REQUEST_ADDRESS )
    .timeout( 0 )
    // 忽略 AuthenticationInterceptor, 還有一大坨沒有貼出來
    .addInterceptor( new AuthenticationInterceptor() )
    .build();

let userDetailClient: UserDetailClient = retrofit.create( UserDetailClient );複製代碼

到如今, retrofit 變量就是一個擁有自動重試, 攔截器鏈, 緩存, 重定向的客戶端對象了.

so, 作完這些事以後, 怎麼搞? 我說, 就這麼搞:

let users = (await userDetailClient.queryByMultiCondition(
    createStartTime, createEndingTime, updateStartTime, updateEndingTime, 
    status, nickname, account, page - 1, range
)).body.result;複製代碼

喲, 就是這樣, 一句話, 不超過100個字符, 但已經等同於一個帶有一切完備功能的異步請求.

我的以爲這纔是 Http 客戶端該有的姿態, 簡潔有力, 直戳痛點.


其實設計也很簡單, 並且這種工具在服務端已經很成熟了, 因此在參考了 retrofit 以後, 我也本身設計了一個可用的雛形, 叫作 retrofitjs.

而在其中, 用到的特性包括 es6 的 Proxy, Decorator, 因此若是真正想用, 只能經過 babel 項目來編譯

其次, 若是用純 JavaScript 實現, 不知道能不能作到兼容 es5, 由於 TypeScript 編譯後, es5 object 是不能繼承 es6 object 的,

因此 TypeScript 實現的支持最多隻能去到 es6.

其實這個項目應該更完善, 好比緩存, 重定向, 自動重發等功能都應該實現, 可是我的時間不容許, 並且我是服務端的人了, 可不能心神不定阿 = =|| 寫出這文章也是爲了告訴你們, 前端的異步請求還能作的更好!

相關文章
相關標籤/搜索