Fetch API 请求封装的最初实现

标签:DX, TypeScript
分类:摘抄本
11分钟阅读

最前

正文

src\Util\Rest.ts
import moment from "moment";
import { RestException } from "./Exceptions";
import { CollectionUtil, StringUtil } from "./Helper";

enum Method {
    GET = "GET",
    POST = "POST",
    PUT = "PUT",
    DELETE = "DELETE"
}

interface ServerError {
    content: string;
    code: string;
    level: string;
}

export const RestDateTimeFormat = "YYYY-MM-DD HH:mm:ss";

class RestUtil {
    private readonly baseUrl = "/api";
    private readonly headers = new Headers([
        ["response-format", "json"],
        ["Accept", "*/*"],
        ["Accept-Encoding", "gzip, deflate"],
        ["Content-Type", "application/json;charset=utf-8"]
    ]);
    private reLoginHandler: () => void;

    private CreateRequestInit = (method: Method): RequestInit => {
        return {
            method: method,
            headers: new Headers(this.headers),
            credentials: "include",
            mode: "same-origin"
        };
    }

    public Get = (url: string) => {
        return new RestRequestWrapper(url, this, this.CreateRequestInit(Method.GET));
    }

    public Post = (url: string) => {
        return new RestRequestWrapper(url, this, this.CreateRequestInit(Method.POST));
    }

    public Put = (url: string) => {
        return new RestRequestWrapper(url, this, this.CreateRequestInit(Method.PUT));
    }

    public Delete = (url: string) => {
        return new RestRequestWrapper(url, this, this.CreateRequestInit(Method.DELETE));
    }

    private GetResponseInternal = async (url: string, requestInt: RequestInit) => {
        const response = await fetch(this.baseUrl + url, requestInt);

        switch (response.status) {
            case 200: //200 客户端请求已成功
            case 204: //204 服务器没有返回内容
                break;

            case 400: // 400 客户端提交的数据在服务器端校验不通过时会收到该代码
            case 500:
                {
                    const text = await response.text();
                    let error: ServerError;
                    try {
                        error = JSON.parse(text) as ServerError;
                    }
                    catch (exception) {
                        console.log(exception);
                        console.log(text);
                    }

                    if (error != null) {
                        throw new RestException(response.url, response.status, error.content, error.code, error.level);
                    }
                    else {
                        if (response.status === 400) {
                            throw new RestException(response.url, response.status, "无效的请求");
                        }
                        else {
                            throw new RestException(response.url, response.status, "服务器异常", null, null, text);
                        }
                    }
                }
            case 401: //401 未授权的用户。用户未登录或者Session过期后去访问服务器的资源会收到该值
                if (this.reLoginHandler != null)
                    this.reLoginHandler();
                throw new RestException(response.url, response.status, "登录超时或用户未授权,请重新登录");
            case 404: //404 资源不存在
                throw new RestException(response.url, response.status, "无法连接服务器");
            default:
                throw new RestException(response.url, response.status, "未知错误");
        }

        return response;
    }

    public GetResponse = (url: string, requestInit: RequestInit) => this.GetResponseInternal(url, requestInit);

    public GetTResponse = async <T>(url: string, requestInit: RequestInit) => {
        const response = await this.GetResponse(url, requestInit);
        const text = await response.text();
        if (!StringUtil.IsNullOrEmpty(text)) {
            return JSON.parse(text) as T;
        } else {
            return null;
        }
    }

    public SetReLoginHandler = (handler: () => void) => {
        this.reLoginHandler = handler;
    }
}

export const Rest = new RestUtil();

其中,RestRequestWrapper 定义如下:

src\Util\Rest.ts
class RestRequestWrapper {
  private url: string;
  private rest: RestUtil;
  private requestInit: RequestInit;
  private parameters = "";

  constructor(url: string, rest: RestUtil, request: RequestInit) {
    this.url = url;
    this.rest = rest;
    this.requestInit = request;
  }

  public AddParameter = (
    key: string,
    value: string | number | moment.Moment,
  ) => {
    if (moment.isMoment(value)) {
      value = value.format(RestDateTimeFormat);
    }
    if (this.parameters.length === 0) {
      this.parameters += `?`;
    } else {
      this.parameters += `&`;
    }
    this.parameters += `${key}=${value}`;
    return this;
  };

  public AddParameterArray = (
    key: string,
    values: Array<string | number | moment.Moment>,
  ) => {
    if (!CollectionUtil.IsNullOrEmpty(values))
      for (const value of values) {
        this.AddParameter(key, value);
      }

    return this;
  };

  public AddBody = (body: Object) => {
    this.requestInit.body = JSON.stringify(body);
    return this;
  };

  public AddHeader = (name: string, value: string) => {
    (this.requestInit.headers as Headers).append(name, value);
    return this;
  };

  public GetTResponseAsync = <T>() => {
    return this.rest.GetTResponse<T>(
      this.url + this.parameters,
      this.requestInit,
    );
  };

  public GetResponseAsync = () => {
    return this.rest.GetResponse(this.url + this.parameters, this.requestInit);
  };
}

Exception.ts
export enum ErrorLevel {
    Error = 1,
    Warning = 2,
}

export class RestException extends Error {
    readonly Code: string;
    readonly Level: ErrorLevel;
    readonly Url: string;
    readonly Status: number;
    readonly MoreDetails: string;

    constructor(url: string, status: number, message: string, code?: string, level?: string, moreDetails?: string) {
        super(message);
        Object.setPrototypeOf(this, RestException.prototype);

        this.Code = code;
        this.Url = url;
        this.Status = status;
        this.MoreDetails = moreDetails;

        if (level === "WARNING") {
            this.Level = ErrorLevel.Warning;
        } else {
            this.Level = ErrorLevel.Error;
        }
    }
}