论声明式 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.

其实这个项目应该更完善, 好比缓存, 重定向, 自动重发等功能都应该实现, 可是我的时间不容许, 并且我是服务端的人了, 可不能心神不定阿 = =|| 写出这文章也是为了告诉你们, 前端的异步请求还能作的更好!

相关文章
相关标签/搜索