RxJS + Fetch: HTTP 请求的新纪元

Reach 是一个使用 RxJS 和原生 Fetch API 构建的 HTTP Client,它受到 Axios 的启发。

RxJS 用于实现类似于 Axios 中拦截器的功能,它被称作管道,是比拦截器更加强大的自定义工具。同时它也尽量使用 Web API 而不是自定义数据类型,这使得它十分的轻量。

它使用起来类似这样:

import {
  RxFetch,
  baseURLKey,
  baseURLPipe,
  retryKey,
  retryPipe,
  timeoutKey,
  timeoutPipe,
  paramKey,
  dataSerialPipe,
  searchParamsPipe,
} from "@tbontb/reach";

const client = new RxFetch({ [baseURLKey]: "https://httpbin.org" });

client.pipes.params
  .use(baseURLPipe)
  .use(searchParamsPipe)
  .use(dataSerialPipe);

client.pipes.response.factory
  .add(timeoutPipe)
  .add(retryPipe);

client
  .get("uuid")
  .then((r) => r.json())
  .then((v) => console.log(v));

client
  .post(
    "delay/1",
    { name: "user name" }, // data
    {
      [timeoutKey]: 3000,
      [paramKey]: { id: "123" },
      [retryKey]: { count: 2, delay: 1000 },
    } // init
  )
  .then((r) => r.json())
  .then((v) => console.log(v));

在这里我们创建了一个 HTTP Client,并添加了一些管道(相当于 Axios 的拦截器)。管道可以让我们对请求参数, Request 和 Response 进行操作,Reach 的所有功能都是使用管道实现的,包括 baseURLsearchParams,超时和重试等等。

HTTP 请求方法的函数签名和原生 Fetch API 一致,其中 post/put/patch 请求方法多了一个 data 参数。

client.get(
  input: string | URL | Request,
  init?: RequestInit
) : Promise<Response>

client.post(
  input: string | URL | Request,
  data: unknown,
  init?: RequestInit
) : Promise<Response>

它的工作流程为:

image.png

管道的位置有三种,分别是 Params 管道,Request 管道和 Response 管道。

函数参数将通过 Params 管道进行处理,并通过 new Request(input, init) 构造请求;请求接着通过 Request 管道,并使用 fetch(request) 得到响应;响应经过 Response 管道处理成为最终的返回值。

每个位置还有三种管道类型,分别为普通管道,错误管道和工厂管道。

函数正常运行时,数据将流经普通管道并经由它们处理;当函数发生错误时,抛出的错误将由错误管道处理。工厂管道接收 inputinit 并返回一个普通管道,这在需要根据参数动态构造管道时十分有用。

例如,baseURLPipe 是一个 Params 普通管道,它的实现如下:

const baseURLKey = Symbol("baseURL");

declare global {
  interface RequestInit {
    [baseURLKey]?: string | URL;
  }
}

RxFetch.init[baseURLKey] = location.origin;

const baseURLPipe: ParamsPipe = ([input, init]) => {
  if (typeof input === "string" && !URL.canParse(input))
    return [new URL(input, init[baseURLKey]), init];
};

当 input 的类型为 string 且不是合法的 URL 时,管道将使用 baseURL 构造合法的 URL 返回。

retryPipe 是一个 Response 工厂管道,它使用 init 中的 timeout 属性和 RxJS 中的 retry 操作符构造一个 Response 普通管道,用于在请求出错时重试。

import { retry, RetryConfig } from "rxjs";

const retryKey = Symbol("retry");

declare global {
  interface RequestInit {
    [retryKey]?: number | RetryConfig;
  }
}

RxFetch.init[retryKey] = { count: 2, delay: 500 };

const retryPipe: ResponseFactory = (_, init) => retry(<number>init[retryKey]);

你可能会注意到上面的代码中使用 Symbol 来定义我们的键,这是因为 RequestInit 并不是我们自定义的新数据类型,而是原生的 Web API。为了防止我们对 Web API 的拓展与将来的标准产生冲突,我们需要用这种方式定义键。

当前项目还处于起步阶段,如果你有好的想法,可以提 issue 或者 PR,项目地址在 https://gitlab.com/tbontb-iaq/reach


42
4.1k 声望4k 粉丝