Narwhals Prolong Mischief

    @alicloud/fetcher
    TypeScript icon, indicating that this package has built-in type declarations

    1.7.6 • Public • Published

    @alicloud/fetcher

    一个类似 axios 的带拦截器的请求包。

    为何不用 axios

    1. not a fan
    2. axios 它底层用的是 XHR 不是 fetch
    3. axios 的 API 设计过于冗长,比如添加拦截器,它是 axios.interceptors.request|response.use,解除用 axios.interceptors.request|response.eject,而且要传入前者返回的值(数字,存在安全风险)
    4. axios request 拦截器是倒着插入的,response 是顺的,用起来非常别扭
    5. axios request 拦截器可以传跟 response 拦截器一样传两个函数
    6. axios request 拦截器一旦出错,真正的请求不会发,但却会触发 response 拦截器中的 fail
    7. axios 对结果做了封装,要拿里边真正的数据就需要剥洋葱
    8. axios 的实例貌似无法「封锁」,每个人都可以来干它,可能谁都不知道谁干了什么,这很容易造成问题
    9. 对 axios 不熟,以上说法可能有失偏颇

    INSTALL

    tnpm i @alicloud/fetcher -S

    Polyfill

    这里对浏览器的依赖主要有两个:fetchPromise

    • fetch 在依赖包 @alicloud/fetcher-fetch 中用 unfetch 做了 fallback,没有全局 polyfill,应用可自行选择,比如 whatwg-fetch
    • Promise 需要应用代码保证

    API

    你可以直接用「开箱即用」的 fetcher:

    import fetcher from '@alicloud/fetcher';
    
    const {
      request,
      jsonp,
      get,
      delete,
      post,
      put,
      patch
    } = fetcher;

    注意,

    1. 里边的方法可以直接析构出来使用
    2. 此开箱即用的 fetcher 无法添加拦截器

    也可以通过工厂方法 createFetcher 创建自己的 fetcher,这样你便可以对它添加拦截器(当然,添加完最后你也可以选择锁定它以保证不被别的代码干扰)。

    import {
      createFetcher
    } from '@alicloud/fetcher';
    
    const fetcher = createFetcher();
    
    const {
      interceptRequest,
      interceptResponse,
      sealInterceptors
    } = fetcher;
    
    // 添加拦截器
    interceptRequest(fn);
    interceptResponse(fnSuccess, fnFail);
    // 锁定拦截器(你可以通过传参选择封锁对请求还是响应的拦截处理)
    sealInterceptors();
    
    export default fetcher;

    封装自己的工厂?

    有的时候,你可能需要封装自己的工厂。

    假设,你封装的将作为一个 npm 包,那么你需要在你的包中最好能够输出这里的 所有输出(包括 type)。

    拦截器

    fetcher 拦截器的设计:

    1. 接口简单:interceptRequestinterceptResponse,它返回一个解除拦截的无参函数
    2. 保证顺序
    3. request 拦截器仅需要一个回调,response 还是保持两个
    4. request 拦截一旦抛错,即中止
    5. response 不会被包裹
    6. 拦截器的最末一个参数是当前的 Fetcher 实例,你可以用它做一些其他的事情,但请保证不会无限循环

    创建拦截器最佳实践

    不好的例子

    一般来说,拦截器可以单独写成 npm 包以便复用,但这个包它不应当有这样的想法:「嗯,调用我的人一定知道怎么使用我。」

    你可能会这么写:

    import {
      Fetcher,
      FetcherConfig,
      FetcherError
    } from '@alicloud/fetcher';
    
    export default (err: FetcherError, fetcherConfig: FetcherConfig, fetcher: Fetcher): void => {
      // do sth. according to err and config
      
      const dealt = dealWithError(err, config);
      
      if (dealt) {
        return;
      }
      
      throw err; // 没处理的错误继续 throw
    };

    但这样写不合适。因为它要求使用者必须了解究竟是用 interceptRequest 还是 interceptResponse,而用 interceptResponse 的时候是第一个回调还是第二个。

    使用者需要对你的 interceptor 十分了解才能用它,像这样:

    import createFetcher, {
      Fetcher
    } from '@alicloud/fetcher';
    import interceptor from 'interceptor-package';
    
    const fetcher: Fetcher = createFetcher();
    
    fetcher.interceptResponse(undefined, interceptor); // 这里会造成困扰,很容易搞错
    
    export default fetcher;

    好的例子

    以上这么做并不是不对,而是不合适,给使用者造成困扰。而更合适的方式,是你告诉使用者:「嘿,把你的 Fetcher 实例给我,剩下的交给我。」

    同时,更更好的是,把你的拦截器,写成一个工厂方法,这样它就可以更具有普适性。

    你可以看仓库中的几个拦截器,都是很好的例子:

    • console-fetcher-interceptor-arms
    • console-fetcher-interceptor-fecs
    • console-fetcher-interceptor-req-mock
    • console-fetcher-interceptor-req-security
    • console-fetcher-interceptor-res-biz
    • console-fetcher-interceptor-res-error-message
    • console-fetcher-interceptor-res-risk
    • console-fetcher-interceptor-sls

    他们的特点:

    src/index export util/intercept 为 default,并可能输出类型 FetcherInterceptorConfigFetcherConfigExtraFetcherConfigExtended

    util/intercept 利用的是可能存在的以下三个文件,它们的输入都是 FetcherInterceptorConfig

    • util/create-interceptor-requestFetcherFnInterceptRequest
    • util/create-interceptor-response-fulfilledFetcherFnInterceptResponseFulfilled
    • util/create-interceptor-response-rejectedFetcherFnInterceptResponseRejected

    类型说明(任何一个都是可选的):

    • FetcherInterceptorConfig 如上所说,是拦截器创建时需要的参数。
    • FetcherConfigExtra 是对 @alicloud/fetcherFetcherConfig 的扩展,用于合适的地方做 interfaceextend
    • FetcherConfigExtended 是扩展后的 FetcherConfig,一般不该被直接用到

    拦截器最佳实践

    src/
     ├── types/
     │   └── index.ts
     ├── util/
     │   ├── ...
     │   ├── create-interceptor-request.ts
     │   ├── create-interceptor-response-fulfilled.ts
     │   ├── create-interceptor-response-rejected.ts
     │   └── intercept.ts
     └── index.ts
    

    src/types/index.ts 范例

    import {
      FetcherConfig
    } from '@alicloud/fetcher';
    
    // 创建拦截器时的配置参数,将被用于 fetcher 工厂的扩展参数
    export interface IFetcherInterceptorConfig {
      ...
    }
    
    export interface IFetcherConfigExtra {
      ...
    }
    
    export interface IFetcherConfigExtended extends FetcherConfig, IFetcherConfigExtra {}

    src/util/create-interceptor-request.ts 范例

    import {
      FetcherFnInterceptRequest,
      FetcherInterceptRequestReturn
    } from '@alicloud/fetcher';
    
    import {
      IFetcherInterceptorConfig,
      IFetcherConfigExtended
    } from '../types';
    
    export default function createInterceptorRequest(interceptorConfig: IFetcherInterceptorConfig): FetcherFnInterceptRequest<IFetcherConfigExtended> {
      // 在这里消费 interceptorConfig
      
      return (fetcherConfig: IFetcherConfigExtended): FetcherInterceptRequestReturn<IFetcherConfigExtended> => {
        ...
      };
    }

    src/util/create-interceptor-response-fulfilled.ts 范例

    import {
      FetcherFnInterceptResponseFulfilled
    } from '@alicloud/fetcher';
    
    import {
      IFetcherInterceptorConfig,
      IFetcherConfigExtended
    } from '../types';
    
    export default function createInterceptorResponseFulfilled(interceptorConfig: IFetcherInterceptorConfig): FetcherFnInterceptResponseFulfilled<IFetcherConfigExtended> {
      // 在这里消费 interceptorConfig
      
      return (o: unknown, fetcherConfig: IFetcherConfigExtended): unknown => {
        ...
      };
    }

    src/util/create-interceptor-response-rejected.ts 范例

    import {
      FetcherError,
      FetcherResponse,
      FetcherFnInterceptResponseRejected,
      FetcherFnRequest
    } from '@alicloud/fetcher';
    
    import {
      IFetcherInterceptorConfig,
      IFetcherConfigExtended
    } from '../types';
    
    export default function createInterceptorResponseRejected(interceptorConfig: IFetcherInterceptorConfig): FetcherFnInterceptResponseRejected<IFetcherConfigExtended> {
      // 在这里消费 interceptorConfig
      
      return (err: FetcherError, fetcherConfig: IFetcherConfigExtended, response: FetcherResponse, request: FetcherFnRequest): void => {
        ...
        
        throw err; // 如果继续错下去就得 throw
      };
    }

    src/util/intercept.ts 范例

    import {
      Fetcher
    } from '@alicloud/fetcher';
    
    import {
      IFetcherInterceptorConfig
    } from '../types';
    
    import createInterceptorRequest from './create-interceptor-request';
    import createInterceptorResponseFulfilled from './create-interceptor-response-fulfilled';
    import createInterceptorResponseRejected from './create-interceptor-response-rejected';
    
    export default function intercept(fetcher: Fetcher, interceptorConfig: IFetcherInterceptorConfig): () => void {
      return fetcher.interceptRequest(createInterceptorRequest(interceptorConfig));
      // 或,任一个可为 undefined
      return fetcher.interceptResponse(createInterceptorResponseFulfilled(interceptorConfig), createInterceptorResponseRejected(interceptorConfig));
    }

    src/index.ts 范例

    export { default } from './util/intercept';
    
    export type {
      IFetcherInterceptorConfig as FetcherInterceptorConfig,
      IFetcherConfigExtra as FetcherConfigExtra,
      IFetcherConfigExtended as FetcherConfigExtended
    } from './types';

    Install

    npm i @alicloud/fetcher

    DownloadsWeekly Downloads

    30

    Version

    1.7.6

    License

    MIT

    Unpacked Size

    141 kB

    Total Files

    121

    Last publish

    Collaborators

    • jacksontian
    • fengmk2
    • pagecao
    • aliyunsdkteam
    • console-fe