用typescript封装Taro.request

概要

        泰罗的request API函数主要用于请求网络服务,可以发起异步请求服务,使用很方便,许多开发工程师都使用过。在实际项目中,会因为一些需求而去封装或者扩展它,最常见的需求是为请求添加token头。在网络上有好几篇介绍如何封装这个函数的文章,从这些文章中收获了不少。前面几个是javascript版本的封装,缺乏强类型支持,这篇文章主要介绍用typescript封装request函数。

扩展的功能

下面这段代码实现主要添加了如下的几个功能点:

  • 如果请求没有携带content-type头,则添加"application/json;charset=UTF-8"类型
  • 根据程序运行环境,选择相应的后端服务地址
  • 在请求头中加入用户的token令牌
  • 返回值统一为ResultDto类型
  • 对后台返回的业务数据进行校验,提前发现数据不匹配的错误
  • 把后台错误,HTTP错误和未知的系统错误,统一归类到ResultDto的错误中
  • 当遇到后台返回的token无效错误时跳到登录页,这里token无效的错误码是10002

实现细节

import Taro from "@tarojs/taro";
import TokenService from "../token/TokenService";

const jsonHader = "application/json;charset=UTF-8";

function getBaseUrl(): string {
  if (process.env.NODE_ENV === "development") {
    return "http://127.0.0.1:9080";
  } else return "";
}

function exist<T>(a: any, ...attrs: T[]): boolean {
  if (process.env.NODE_ENV === "development") {
    for (let i = 0; i < attrs.length; ++i) {
      let item = attrs[i];
      if (a[item] != "" && !a[item]) {
        return false;
      }
    }
  }
  return true;
}

export const HTTP_STATUS = {
  SUCCESS: 200,
  CREATED: 201,
  ACCEPTED: 202,
  CLIENT_ERROR: 400,
  AUTHENTICATE: 401,
  FORBIDDEN: 403,
  NOT_FOUND: 404,
  SERVER_ERROR: 500,
  NOT_IMPLEMENTED: 501,
  BAD_GATEWAY: 502,
  SERVICE_UNAVAILABLE: 503,
  GATEWAY_TIMEOUT: 504,
};

export const REFRESH_STATUS = {
  NORMAL: 0,
  REFRESHING: 1,
  NO_MORE_DATA: 2,
};

export const getCurrentPageUrl = (): string => {
  let pages = Taro.getCurrentPages();
  let currentPage = pages[pages.length - 1];
  let url = currentPage.route;
  return url || "";
};

export const pageToLogin = () => {
  TokenService.clear();
  let path = getCurrentPageUrl();
  if (!path.includes("login")) {
    Taro.reLaunch({
      url: "/pages/login/index",
    });
  }
};

export type ResultDto<T> = {
  success: boolean;
  // 错误编号
  errorCode?: string;
  // 错误描述
  errorMessage?: string;
  // 具体对象
  data?: T;
  // error display type: 0 silent; 1 message.warn; 2 message.error; 4 notification; 9 page
  showType?: number;
  // Convenient for back-end Troubleshooting: unique request ID
  traceId?: string;
  // onvenient for backend Troubleshooting: host of current access server
  host?: string;
};

const tokenInterceptor = (chain: Taro.Chain) => {
  const requestParams = chain.requestParams;
  const { header } = requestParams;
  let token = TokenService.load();
  const tokenHeader = {
    Authorization: `Bearer ${token}`,
    // "content-type": jsonHader,
  };
  requestParams.header = { ...tokenHeader, ...header };
  return chain.proceed(requestParams);
};

// Taro 提供了两个内置拦截器
// logInterceptor - 用于打印请求的相关信息
// timeoutInterceptor - 在请求超时时抛出错误。
// const interceptors = [customInterceptor, Taro.interceptors.logInterceptor]
const interceptors = [tokenInterceptor];
interceptors.forEach((interceptorItem) => Taro.addInterceptor(interceptorItem));

type FilterOptional<T extends object> = Pick<T, Exclude<{ [K in keyof T]: T extends Record<K, T[K]> ? K : never }[keyof T], undefined>>
type kType<T extends object> = keyof FilterOptional<T>;

const request = <T extends object>(params: Taro.request.Option, ...attrs: kType<T>[]): Promise<ResultDto<T>> => {
  let { url, header } = params;
  const baseUrl = getBaseUrl();
  const url2 = baseUrl + url;
  let contentType = jsonHader;
  contentType = header?.contentType || jsonHader;
  const option = {
    ...params,
    header: { "content-type": contentType },
    timeout: 50000,
    url: url2,
  };

  Taro.showLoading({
    title: "加载中",
  });
  return Taro.request(option)
    .then((res: Taro.request.SuccessCallbackResult<any>) => {
      const pos = contentType.indexOf("application/json");
      const { statusCode, data } = res;
      if (pos == -1) {
        return { success: true, data };
      }
      // 只要请求成功,不管返回什么状态码,都走这个回调
      if (statusCode == HTTP_STATUS.SUCCESS) {
        if (data?.success) {
          // 成功且取到了数据
          if(!exist(data.data, ...attrs)){
            console.error("返回值不包含必需的字段", data.data, attrs);
            return Promise.resolve({...data, success: false, errorCode: "BizError", errorMessage: "返回值不能匹配"});
          }
          return Promise.resolve(data);
        }
        // 成功,但处理过程报错了
        let dto: ResultDto<T> = data;
        console.warn(
          `url =${url2}, traceid=${dto.traceId}, error code=${dto.errorCode}, error msg=${dto.errorMessage}`
        );
        if(dto.errorCode === "10002"){
          pageToLogin();
        }
        Promise.resolve(dto);
      }

      let dto: ResultDto<T> = {
        success: false,
        errorCode: statusCode + "",
        errorMessage: `http status: ${statusCode}`,
      };
      if (statusCode === HTTP_STATUS.NOT_FOUND) {
        dto.errorMessage = "请求资源不存在";
      } else if (statusCode === HTTP_STATUS.FORBIDDEN) {
        dto.errorMessage = "没有权限访问";
      } else if (statusCode === HTTP_STATUS.AUTHENTICATE) {
        dto.errorMessage = "需要鉴权";
      } else if (statusCode === HTTP_STATUS.SERVER_ERROR) {
        dto.errorMessage = "服务器错误";
      } else if (statusCode === HTTP_STATUS.NOT_IMPLEMENTED) {
        dto.errorMessage = "服务没有实现";
      } else if (statusCode === HTTP_STATUS.BAD_GATEWAY) {
        dto.errorMessage = "服务网关出现了问题";
      } else if (statusCode === HTTP_STATUS.SERVICE_UNAVAILABLE) {
        dto.errorMessage = "服务器无法处理请求";
      }
      Taro.showToast({ title: dto.errorMessage || "", icon: "error" });
      return Promise.resolve(dto);
    })
    .catch((error) => {
      console.error("http return error,", error);
      return Promise.resolve({
        success: false,
        errorCode: "system",
        errorMessage: error.toString(),
      });
    })
    .finally(() => Taro.hideLoading());
};

export default request;

上面代码中的TokenService类,提供了token的本地和reducer存取功能,这个类也引用了其它的文件,这里不继续展开。        

import Taro from '@tarojs/taro'
import store from '../../store'
import {setValue} from '../../store/model/token'
import StringUtil from '@/utils/stringUtil';

export default class TokenService{

    public static save(data: string): void{
        Taro.setStorage({
            key: "token",
            data
        })
        store.dispatch(setValue({token: data}));
    }

    public static load(): string {
        const {TokenStateReducer} =store.getState();
        if(!StringUtil.isEmpty(TokenStateReducer.token)){
            return TokenStateReducer.token;
        }
        try {
            const token = Taro.getStorageSync<string>('token');
            store.dispatch(setValue({token}));
            return token
          } catch (e) {
            console.log("can not read token in storage");
            return "";
          }
    }

    public static clear(): void {
        TokenService.save("");
    }
}

如何使用

这里举一个获取图形验证码的例子

import request, { ResultDto } from '../request'
export type CaptchaResponseDto = {
  key: string;
  code?: string;
  image: string;
}

// 获取图片验证码
export async function getCaptchaImage(): Promise<ResultDto<CaptchaResponseDto>>{
    return request<CaptchaResponseDto>({url: "/public/image-captcha", method: 'GET'}, "key", "image");
}

总结

        通过上述的封装,getCaptchaImage使用request函数更加简单,同时也具有了完备的类型支持,提高了系统的可读性和可维护性。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值