npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

cloud-sea

v1.0.2

Published

封装了一些常见的业务逻辑的请求库,如请求参数与返回数据处理,业务逻辑码(retcode)处理,失败重试,统一成功失败处理等。默认支持小程序与 WEB 请求。

Downloads

5

Readme

CloudSea

CloudSea 是一种封装了一些常见的业务逻辑的请求库,如请求参数与返回数据处理,业务逻辑码(retcode)处理,失败重试,统一成功失败处理等。默认支持小程序与 WEB 请求,可自定义适配器支持更多不同请求。

特征

  • baseUrl 设置
  • 失败后自动 2 次重启
  • 业务返回码 retcode 字段指定及白名单处理
  • request 请求参数插件处理
  • response 返回数据插件处理
  • 完成处理(不论成功或失败),见下面的 completeHandler(可在里面进行 hideLoading 处理、上报处理等)
  • 成功或失败的统一 promise 链,见下面的 thenHandler 和 catchHandler
  • 支持取消发送请求
  • 请求适配器自定义
  • Typescript 编写
  • 其他定制

安装

npm i cloud-sea --save

使用

光速入门

import { CloudSea } from 'cloud-sea';

const gRequest = new CloudSea();

const requestData = {
  url: 'xxx',
  data: {
    a: 1,
  },
};

// request 调用,如果没有指定 method,则为 GET 请求
gRequest.request(requestData);

// get 调用
gRequest.get(requestData);

// post 调用
gRequest.post(requestData);

手把手自定义

1、实例化

这里实例化的参数分为两类,一类是用于真正请求的,如 header,timeout 等;另一类是用于辅助控制逻辑的,会统一放到 ext 对象中。

import { CloudSea, ICloudSeaConfig } from 'cloud-sea';

// 在默认配置参数上扩展新的字段 loadingTips,用于控制是否显示 loading
interface IExt extends ICloudSeaConfig {
  loadingTips: boolean;
}

// ICloudSeaOptions 的结构大概是 {..., ext?: {...}}
// 前面三点省略表示请求的一些字段,后面三点表示 config 字段
// 这里 options.ext 里面的配置会覆盖默认的配置
const gRequest = new CloudSea<IExt>({
  ext: {
    loadingTips: false, // 新增的字段
    logicErrorMsgKey: 'msg', // 覆盖默认的字段
  },
});

// 或通过 setConfig 方法修改默认的配置字段
gRequest.setConfig({
  logicErrorMsgKey: 'msg', // 覆盖默认的字段
});

// 如果没有新增字段,则不需要泛型
// const gRequest = new Request({ ext: {logicErrorMsgKey: 'msg'} });

export default gRequest;

默认的一些用于辅助控制逻辑的参数,如下(可通过 setConfig 方法或实例化自定义参数覆盖):

{
  baseUrl: '',
  xRequestId: true, // header 头部加上 x-request-id,方便打通日志链路,默认开启
  repeatNum: 2, // 默认失败自动重试 2 次
  timeout: 0, // 默认请求本身支持 timeout 参数的不需要这个,设置为 0 即可,如果需要兼容不支持的,设置这个即可,不要设置请求本身的,qq 小程序本身不支持 timeout 设置
  retcodeKey: 'retcode', // retcode 字段,false 表示不启用该功能
  retcodeWhiteList: [], // retcode 白名单,默认空数组为retcode为0进入then,其余为进入catch,false 表示不启用该功能,retcodeKey 为 false 也没有该功能
  logicErrorMsgKey: 'message', // ,默认逻辑错误文本字段,支持一层对象,如 errData.msg
  loginErrorMsgUnknown: 'Unknown Error', // 逻辑错误文本默认,如果后台没有返回具体的错误,则显示该文本
  adapter: getDefaultAdapter(), // 自动适配 WEB 还是小程序发送请求
}

2、插件处理入参与返回

// 插件式处理请求参数或返回数据
// ------------------------------------------------
// 处理请求参数
// 可使用多个 use,每个 use 函数按注册顺序依次进入管道处理,所有的入参都挂到 ctx.req 对象上,最后返回 ctx
// 如小程序可再次设置 header 的 cookie 字段,用于鉴权
gRequest.req.use((ctx) => {
  // 处理 req ...
  // ctx.req.xxx = xxx;

  // 最后记得 return ctx
  return ctx;
});

// 处理返回数据,所有的返回都挂到 ctx.res 对象上,同上请求参数的处理
gRequest.res.use((ctx) => {
  // 处理 res ...
  // ctx.res = {};

  // 最后记得 return ctx
  return ctx;
});

3、成功失败的统一处理

// 处理请求的成功及失败
// ------------------------------------------------
// 请求完成处理,不论成功或失败,可用于关闭 loading,上报等
// err 有三种 type,分别为:逻辑错误,服务器错误,网络错误,具体见错误说明部分
gRequest.completeHandler = (ctx, err?) => {
  // 成功
  if (!err) {
  }

  // 可以根据 err.type 来判断错误类型
  // ------------------------------------
  // fail
  if (err.type === REQUEST_ERROR_MAP.fail) {
  }

  // server
  if (err.type === REQUEST_ERROR_MAP.server) {
  }

  // logic
  if (err.type === REQUEST_ERROR_MAP.server) {
  }
};

// 统一成功处理
gRequest.thenHandler = (ctx) => {
  // 返回下一步进入成功的数据
  return ctx.res;
};

// 统一错误处理:如 toast 提示,上报错误等
gRequest.catchHandler = (err, ctx) => {
  // 处理逻辑
};

4、函数封装

// 常用 get 与 post 方法的进一步封装
// ------------------------------------------------
// 先定义返回的数据格式
export interface IRequestRes<T> {
  retcode: number;
  data: T;
  cost: number;
  message: string;
}
// 简化 get 方法
// 如果实例化有泛型,则这里的 IRequestOptions 也需要 <IExt>,否则不需要
export function gGet<T>(data: IRequestOptions<IExt>) {
  return gRequest.get<IRequestRes<T>>(data);
}

// 简化 post 方法
// 如果实例化有泛型,则这里的 IRequestOptions 也需要 <IExt>,否则不需要
export function gPost<T>(data: IRequestOptions<IExt>) {
  return gRequest.post<IRequestRes<T>>(data);
}

5、具体发送请求

导入 gRequest.ts,调用其方法发送具体请求

// api.ts
// ------------------------------------
import { IRequestOptions, ICloudSeaConfig } from 'cloud-sea';
import gRequest, { gGet, gPost } from './gRequest';

// 定义返回结构
interface Res<T> {
  retcode: number;
  data: T;
  message?: string;
}

// 常用的请求参数类型如下,具体的话可见类型提示:
// {
//   url: string;
//   method?: IMethod;
//   header?: Record<string, string>;
//   data?: IAnyObject | ...; // 这个有多种值,xhr 这里只做做了 IAnyObject 的特殊处理,其余全部透传
//   timeout?: number; // 注意该 timeout 为默认支持的,如果确认你要兼容的都支持,那么就可以考虑去掉 ext 中的 timeout
//   ext?: {...}; // 上面说的配置
//   task?: { name: string} // 用于取消请求用
// }

//

// 请求 1
interface Data1 {
  isValid: boolean;
}

// 使用 request 方法发送
gRequest.request<Res<Data1>>({
  url: 'xxx',
  method: 'POST',
  header: {
    // 设置 header
    'x-language': 'en', // 设置请求语言
  },
  data: {
    a: 1,
    b: 2,
  },
  // 可覆盖前面的设置
  ext: {
    repeatNum: 0, // 失败不重试
  },
});

// 或直接采用 gPost 方法发送
gPost<Res<Data1>>({
  url: 'xxx',
  header: {
    // 设置 header
    'x-language': 'en', // 设置请求语言
  },
  data: {
    a: 1,
    b: 2,
  },
  // 可覆盖前面的设置
  ext: {
    repeatNum: 0, // 失败不重试
  },
});

// get 请求
interface Data2 {
  name: string;
}
gGet<Res<Data2>>({
  url: 'xxx',
  // 自动拼成 query
  data: {
    a: 1,
    b: 2,
  },
  ext: {
    retcodeKey: 'code', // 这条请求的 retcodeKey 是 code 字段
  },
});

// post 请求
interface Data3 {
  name: string;
  id: string;
}
gPost<Res<Data3>>({
  url: 'xxx',
  data: {
    a: 1,
    b: 2,
  },
  ext: {
    retcodeWhiteList: [3455, 6784], // retcode 为 0, 3455,6784 将会按成功处理,其余按失败处理
  },
});

6、取消正在发送中的请求

// 给请求取个名字,注意只有请求有了 name 值才能被取消,否则不能被取消
interface Data3 {
  name: string;
  id: string;
}
gPost<Res<Data3>>({
  url: 'xxx',
  task: {
    name: 'hello',
  },
});

// 取消单个 taskName 为 hello 的请求
gRequest.abort('hello');

// 取消所有有请求名的正在发送中的请求
gRequest.abort();

ctx 参数说明

ctx 由 req、res、ext 三大属性组成, 其 TS 类型如下:

PS:注意所有的用于辅助功能的参数都挂到 ext 上,不要随便在 req 上面挂属性。req 会透传到请求适配器,用于发送请求的所有参数。

export interface ICloudSeaConfig {
  baseUrl: string; // 基础 url,以 https 开头
  repeatNum: number; // 请求失败重试次数
  xRequestId: boolean; // 是否生成请求 id
  timeout: number; // 超时时间,如果确定发请求的 api 本身就支持 timeout 属性,可以设置该值为 0
  retcodeKey: false | string | string[]; // retcode 字段,false 表示不启用该功能
  retcodeWhiteList: false | number[]; // retcode 白名单,默认 0 和 白名单表示业务成功,其余为失败,false 表示不启用该功能。
  logicErrorMsgKey: string; // 业务逻辑错误文本字段
  logicErrorMsgUnknown: string; // 默认的业务逻辑错误文本,如果后台没有返回对应的错误信息,则将使用该信息
  adapter: IAdapter | undefined; // 请求 adapter
}

export interface IRequestTask {
  name?: string; // 用于取消请求
  client: any;
  urlHasNoSearch: string; // 去掉请求 url 的 query,可用于上报请求地址
  timer: ReturnType<typeof setTimeout>;
  costTime: number; // 请求总共花费时间,当 xRequestTime 为 true,则有该值
  repeatTry: () => Promise<IAnyObject>; // 用于重试
}

export interface IRequestCtx<T extends ICloudSeaConfig> {
  req: IReqOptions & { header: Record<string, string> };
  res: IRequestSuccessCallbackResult | Record<string, never>;
  ext: T;
  // ext: U extends Record<string, never> ? ICloudSeaConfig : ICloudSeaConfig & U;
  task: IRequestTask;
}

export type IRequestOptions<T extends Partial<ICloudSeaConfig>> = IReqOptions & {
  ext?: T;
} & { task?: { name: string } };

export type ICloudSeaOptions<T extends Partial<ICloudSeaConfig>> = Omit<IRequestOptions<T>, 'url' | 'data' | 'task'>;

请求错误说明

import { REQUEST_ERROR_MAP, RequestError, DEFAULT_LOGIC_ERROR_MSG_UNKNOWN } from 'cloud-sea';

// REQUEST_ERROR_MAP
// {
//   fail: 'REQUEST_ERROR_FAIL', // 直接 fail 的错误
//   server: 'REQUEST_ERROR_SERVER', // 服务端错误,statusCode 小于 200,大于等于 300
//   logic: 'REQUEST_ERROR_LOGIC', // 业务逻辑错误
// }

1、第一种错误:根本没有收到服务端返回的信息,这就有了 fail 的错误(以小程序来说,就是 fail 的回调,以 web 来说,就是 XMLHttpRequest 的 onabort, ontimeout, onerror 事件触发),该错误抛出:new RequestError(err.message || err.errMsg, { type: REQUEST_ERROR_MAP.fail })

2、第二种错误:接受到了服务端的信息,但是 statusCode 小于 200,大于等于 300,这就有了 server 错误,该错误抛出: new RequestError('Request Server Error', { type: REQUEST_ERROR_MAP.server, statusCode });

3、第三种错误:虽然 statusCode 大于等于 200,小于 300,但是如果用户没有登录,或身份不对等都无法获取到正确的数据,所以就有了 logic 错误,该错误抛出: new RequestError(logicErrMsg, { type: REQUEST_ERROR_MAP.logic, retcode });logicErrMsg 的取值见下面的说明。

这三种抛出的错误默认都会进入到 catchHandler 进行处理,如果对 retcodeWhiteList 进行设置,则第三种的 logic 错误白名单内的会当做成功进入到 thenHandler 处理。

catchHandlercompleteHandler 的 err 参数,就是上面的三种错误实例。

logicErrMsg

logicErrMsg 是通过调用 getLogicErrMsg 方法得到的,逻辑如下:

getLogicErrMsg(ctx: IRequestCtx): string {
  const { res, ext } = ctx;
  const { logicErrorMsgKey, logicErrorMsgUnknown } = ext;

  // 如果没有指定 msg 的 key
  if (!logicErrorMsgKey) {
    return logicErrorMsgUnknown;
  }

  // 如果有指定,则取该值
  let logicErrMsg: string | undefined = res.data?.[logicErrorMsgKey];
  // 错误信息可能不是一个直接的 string 字段,而是被包裹在一个对象中,如 errData: { text: 'xxx', code: xxx };
  // 这样可以设置 key 值为 'errData.text',分割得到最终的字段
  if (!logicErrMsg && logicErrorMsgKey.includes('.')) {
    const [key1, key2] = logicErrorMsgKey.split('.');
    if (res.data?.[key1]?.[key2]) {
      logicErrMsg = res.data[key1][key2];
    }
  }

  return logicErrMsg || logicErrorMsgUnknown;
}

实战

import { CloudSea, RequestError, ICloudSeaConfig, IRequestOptions } from 'cloud-sea';
// 如为小程序,可直接使用 `wx.showLoading` 与 `wx.showToast` 来实现。
import { showLoading, hideLoading, showToast } from 'x-global-api';
import humps from 'humps';

interface IExt extends ICloudSeaConfig {
  loadingTips: boolean; // 请求发送时 showLoading
  camelCase: boolean; // 失败 showToast
  failToast: boolean; // 转驼峰处理
}

const gRequest = new CloudSea<IExt>({
  ext: {
    baseUrl: 'xxx', // xxx 表示以 https 开头的 url 前缀
    loadingTips: false,
    camelCase: true,
    failToast: true,
  },
});

gRequest.req.use((ctx) => {
  // 默认如果 loadingTips 为 true,则显示 loading
  if (ctx.ext.loadingTips) {
    showLoading({ title: '数据加载中...', mask: true });
  }

  return ctx;
});

gRequest.res.use((ctx) => {
  // 默认如果 camelCase 为 true,则自动进行转驼峰处理
  if (ctx.ext.camelCase) {
    ctx.res.data = humps.camelizeKeys(ctx.res.data);
  }

  return ctx;
});

// 不管成功,失败都要 hideLoading
gRequest.completeHandler = (ctx, err?) => {
  if (ctx.ext.loadingTips) {
    hideLoading();
  }
};

// 统一成功处理
gRequest.thenHandler = (ctx) => {
  // 返回下一步进入成功的数据
  return ctx.res.data;
};

// 失败错误提示
gRequest.catchHandler = (err, ctx) => {
  if (ctx.ext.failToast) {
    const { message, retcode, type, statusCode } = err || {};
    const codeText = retcode ? `(${retcode})` : '';

    if (message) {
      showToast({
        title: type === RequestError.fail ? '请求失败' : `${message}${codeText}`,
        icon: 'none',
      });
    }
  }
};

export function gGet<T>(data: IRequestOptions<Partial<IExt>>) {
  return gRequest.get<IRequestRes<T>>(data);
}

export function gPost<T>(data: IRequestOptions<Partial<IExt>>) {
  return gRequest.post<IRequestRes<T>>(data);
}

export default gRequest;

注意事项

  • 该库 TS 编译的 targetES2015。如果要兼容到老版本,请再进行一次 babel 编译。