Skip to content

fetch 函数封装

更新: 2025/3/13 17:45:22 字数: 0 字

fetch 函数封装

ts
import {getTokenUtil} from "@/utils/tokenUtil";
import {getModalSystemConfig, globalMessageApi, globalModalApi} from "@/components/OpComponent/Message";
import store from "@/store";
import {AsyncUserLogout} from "@/store/modules/userSlice.ts";


/*请求类型*/
export enum RequestMethod {
    GET = 'get',
    POST = 'post',
    PUT = 'put',
    DELETE = 'delete',
    PATCH = 'patch',
}

type PrFetchOptions = {
    baseURL?: string;
    timeout?: number;
    method?: string,  // 请求方式,默认是get,可以在这里设置默认方式为其他请求方式
    headers?: {
        [key: string]: string
    },
}

type Function = (...args: any[]) => any;

class Request {
    baseURL: string;
    timeout: number | null;
    _method: string;
    _headers: object;
    private requestInterceptors: Array<[Function, Function]> = [];
    private responseInterceptors: Array<[Function, Function]> = [];

    constructor(baseURL: string, timeout: number | null, method: string, headers: object) {
        this.baseURL = baseURL || "";
        this.timeout = timeout || null;
        this._method = method || 'get';
        this._headers = headers || {};
    }


    // 添加请求拦截器
    public interceptors = {
        request: {
            use: (onFulfilled: Function, onRejected: Function) => {
                this.requestInterceptors.push([onFulfilled, onRejected]);
            }
        },
        response: {
            use: (onFulfilled: Function, onRejected: Function) => {
                this.responseInterceptors.push([onFulfilled, onRejected]);
            }
        }
    };


    /**
     * 检查 HTTP 状态码
     * @param response
     */
    checkHttpStatus = (response: Response) => {
        const {status: statusCode, statusText, url} = response;
        if (statusCode >= 200 && statusCode < 300) {
            return response;
        }
        const msg = `请求错误: ${statusCode}[${statusText}]`;
        return Promise.reject({
            url,
            msg,
            code: statusCode,
        });
    };


    /**
     * 发送请求
     * @param url
     * @param options 发送请求之前的配置
     */
    async fetch(url: string, options: any): Promise<any> {
        let config = {
            ...options,
            url: this.baseURL + url,
            method: options.method || this._method,
            headers: {
                ...this._headers,
                ...options.headers,
            },
            params: options.params || null,
            body: options.data || null,
        };
        if (options.data instanceof FormData) {
            config.body = options.data
            delete config.headers['Content-Type']
        }

        if (config.headers["Content-Type"] == "application/json") {
            config.body = JSON.stringify(options.data) || null
        }

        // 如果有 params 对 其镜像拼接,加在 url 中, 但是值不能是 null 和 undefined
        if (options.params) {
            for (const key in options.params) {
                if (options.params[key] === null || options.params[key] === undefined) {
                    delete options.params[key];
                }
            }
        }
        if (options.params) {
            const params = new URLSearchParams(options.params).toString();
            config.url += '?' + params;
        }
        const controller = new AbortController();
        const hasTimeOut: boolean = !options.isTimeout;
        if (hasTimeOut) {
            config.timeout = options.timeout || this.timeout;
        }

        // 应用请求拦截器
        for (const [onFulfilled, onRejected] of this.requestInterceptors) {
            try {
                // 构建好请求配置
                config = await onFulfilled(config);
            } catch (error) {
                return onRejected(error);
            }
        }

        try {
            const result =
                await (config.hasTimeOut ?
                    Promise.race([
                        fetch(config.url,
                            {...config, signal: controller.signal}),
                        new Promise((_, reject) =>
                            setTimeout(() => {
                                controller.abort();
                                reject(new Error("request time out"))
                            }, config.timeout))
                    ]) : fetch(config.url, config))
                    .then(response => response as Response)
                    .then(this.checkHttpStatus)
            let data = await result.json();
            // 应用响应拦截器
            for (const [onFulfilled, onRejected] of this.responseInterceptors) {
                try {
                    data = await onFulfilled(data);
                } catch (error) {
                    // 这里就是返回失败,直接抛出,不再走拦截器,后面会被 catch 捕获
                    return Promise.reject("失败");
                }
            }
            return data;
        } catch (error) {

            console.log("error========", error)
            // 处理请求错误
            for (const [, onRejected] of this.responseInterceptors) {
                // eslint-disable-next-line no-useless-catch
                try {
                    return await onRejected(error);
                } catch (e) {
                    // 如果所有拦截器都处理失败,抛出最终错误
                    throw e;
                }
            }
            throw error;
        }
    }
}

/**
 * 创建请求,希望可以将其封装成一个通用请求方法
 * @param option
 */
function createPrFetch(option: PrFetchOptions) {
    // 创建请求实例对象
    const requestInstance = new Request(option.baseURL || "", option.timeout || null, option.method || "get", option.headers || {});
    return {
        fetch: (options: any) => requestInstance.fetch(options.url, options),
        interceptors: requestInstance.interceptors,
    }
}


export const isReLogin = {
    show: false
}

// 配置基础 URL 和超时时间,创建 request 实例
const request = createPrFetch({
    baseURL: '/pro-api',
    timeout: 10000,
    headers: {
        "Content-Type": "application/json",
    },
});


// 配置请求拦截器
request.interceptors.request.use(
    (config: any) => {
        // 配置 token
        if (!config.hasToken) {
            config.headers.Authorization = 'Bearer ' + getTokenUtil();
        }
        return config;
    },
    (error: any) => {
        return Promise.reject(error);
    }
);

// 配置响应拦截器
request.interceptors.response.use(
    async (response: any) => {
        // 进行 code | msg 统一处理
        const code = response.code;
        const msg = response.msg || "未知错误";
        console.log("response", response)
        if (code === 500) {
            globalMessageApi.error(msg)
            console.log("未知错误")
            return Promise.reject(response);
        } else if (code === 401) {
            if (!isReLogin.show) {
                globalMessageApi.error("登录状态已过期,请重新登录")
                isReLogin.show = true;
                try {
                    globalModalApi.confirm(getModalSystemConfig("登录状态已过期,请重新登录")).then((res: any) => {
                        if (res) {
                            store.dispatch(AsyncUserLogout()).finally(() => {
                                // 路由到 /login 页面
                                // router.navigate("/login");
                                isReLogin.show = false;
                            })
                        }
                    })
                } catch (e) {
                    console.log(e)
                }
            }
        } else if (code !== 200) {
            globalMessageApi.error(response.msg)
            return Promise.reject(response);
        }
        return response;
    },
    (error: any) => {
        console.log("error——————————", error)
        let { msg} = error;
        if (msg == "Network Error") {
            msg = "后端接口连接异常";
        } else if (msg.includes("timeout") || msg.includes("Timeout")) {
            msg = "系统接口请求超时";
        } else if (msg.includes("Request failed with status code")) {
            msg = "系统接口" + msg.substr(msg.length - 3) + "异常";
        }
        // ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
        // message.error()
        globalMessageApi.error(msg)
        return Promise.reject(error);
    }
);

/*
* TODO 额外对外暴露方法,在调用 fetch 之前可以链式调用 requestHandler 去个性化配置请求,从而再次进行请求
*  主要用于个性化配置
* */
export default request.fetch;

道友再会.