前端程序员:我写了一个超牛的 API 工具,老板直呼“内行”!

94 阅读4分钟

开场白:

大家好,我是你们的老朋友,一个每天都在和 Bug 斗智斗勇的前端程序员。今天我要给大家分享一个我最近写的“神器”——一个超级强大的 API 客户端工具。它不仅让我的工作效率翻倍,还让我的老板直呼“内行”!如果你也在为网络请求的复杂性头疼,那这篇文章你一定要看完!


正文:

1. 为什么我要写这个工具?

作为一个前端程序员,我每天都要和 API 打交道。GET、POST、PUT、DELETE……这些 HTTP 方法就像我的“老朋友”,但有时候它们也会让我抓狂。比如:

  • 请求超时了怎么办?
  • 网络波动导致请求失败了怎么办?
  • 用户突然离开页面,未完成的请求怎么取消?
  • 重复请求同一个接口,能不能缓存一下?

于是,我决定自己动手,写一个“万能”的 API 工具,解决这些问题!经过一番折腾,终于搞定了,效果还不错,老板看了直呼“内行”!


2. 这个工具到底有多牛?

先来看看它的核心功能:

  • 请求超时控制:再也不用担心请求卡死了,超时自动取消!
  • 请求重试机制:网络波动?不怕!自动重试,最多 3 次!
  • 请求取消功能:用户突然离开页面?一键取消未完成的请求!
  • 请求缓存:重复请求同一个接口?直接返回缓存结果,性能提升 100%!
  • 并发控制:同时发起太多请求?自动排队,避免资源耗尽!
  • 灵活的拦截器:想在请求前后加点“私货”?拦截器帮你搞定!

是不是听起来就很厉害?别急,代码在后面,咱们慢慢看!


3. 代码展示:一看就懂,一学就会!

以下是这个工具的完整代码(基于 JavaScript):

class Api {
    constructor(baseUrl, headers = {}, maxConcurrentRequests = 5) {
        this.baseUrl = baseUrl;
        this.headers = {
            'Content-Type': 'application/json',
            ...headers,
        };
        this.requestInterceptors = [];
        this.responseInterceptors = [];
        this.maxConcurrentRequests = maxConcurrentRequests;
        this.currentRequests = 0;
        this.requestQueue = [];
        this.cache = new Map();
        this.abortControllers = new Map();
    }

    async request(endpoint, method, body = null, credentials = 'omit', timeout = 10000, retries = 3, useCache = false, customConfig = {}) {
        const cacheKey = `${method}:${endpoint}`;

        // 使用缓存
        if (useCache && this.cache.has(cacheKey)) {
            return this.cache.get(cacheKey);
        }

        // 并发控制
        if (this.currentRequests >= this.maxConcurrentRequests) {
            await new Promise((resolve) => this.requestQueue.push(resolve));
        }

        this.currentRequests++;

        let lastError;
        for (let i = 0; i < retries; i++) {
            try {
                const result = await this._request(endpoint, method, body, credentials, timeout, customConfig);
                if (useCache) {
                    this.cache.set(cacheKey, result);
                }
                return result;
            } catch (error) {
                lastError = error;
                if (i === retries - 1) {
                    throw lastError;
                }
            }
        }
    }

    async _request(endpoint, method, body, credentials, timeout, customConfig) {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), timeout);

        // 存储 controller 以便后续取消
        this.abortControllers.set(endpoint, controller);

        let config = {
            method,
            headers: this.headers,
            credentials,
            signal: controller.signal,
            ...customConfig,
        };

        if (body) {
            config.body = JSON.stringify(body);
        }

        // 执行请求拦截器
        for (let interceptor of this.requestInterceptors) {
            config = await interceptor(config, endpoint, method);
        }

        const url = `${this.baseUrl}${endpoint}`;
        let response;
        try {
            response = await fetch(url, config);
            if (!response.ok) {
                throw new ApiError(`HTTP 错误! 状态码: ${response.status}`, response.status, response, config);
            }
        } catch (error) {
            console.error('请求失败:', error);
            throw error;
        } finally {
            clearTimeout(timeoutId);
            this.abortControllers.delete(endpoint);
            this.currentRequests--;
            if (this.requestQueue.length > 0) {
                this.requestQueue.shift()();
            }
        }

        // 执行响应拦截器
        for (let interceptor of this.responseInterceptors) {
            response = await interceptor(response, endpoint, method);
        }

        return await response.json();
    }

    async get(endpoint, queryParams = {}, credentials = 'omit', timeout = 10000, retries = 3, useCache = false) {
        let fullEndpoint = endpoint;
        if (Object.keys(queryParams).length > 0) {
            const queryString = new URLSearchParams(queryParams).toString();
            fullEndpoint += `?${queryString}`;
        }
        return this.request(fullEndpoint, 'GET', null, credentials, timeout, retries, useCache);
    }

    async post(endpoint, body, credentials = 'omit', timeout = 10000, retries = 3) {
        return this.request(endpoint, 'POST', body, credentials, timeout, retries);
    }

    async put(endpoint, body, credentials = 'omit', timeout = 10000, retries = 3) {
        return this.request(endpoint, 'PUT', body, credentials, timeout, retries);
    }

    async patch(endpoint, body, credentials = 'omit', timeout = 10000, retries = 3) {
        return this.request(endpoint, 'PATCH', body, credentials, timeout, retries);
    }

    async delete(endpoint, credentials = 'omit', timeout = 10000, retries = 3) {
        return this.request(endpoint, 'DELETE', null, credentials, timeout, retries);
    }

    useRequestInterceptor(interceptor) {
        this.requestInterceptors.push(interceptor);
    }

    useResponseInterceptor(interceptor) {
        this.responseInterceptors.push(interceptor);
    }

    cancelRequest(endpoint) {
        if (this.abortControllers.has(endpoint)) {
            this.abortControllers.get(endpoint).abort();
            this.abortControllers.delete(endpoint);
        }
    }

    clearCache() {
        this.cache.clear();
    }
}

class ApiError extends Error {
    constructor(message, statusCode, response, config) {
        super(message);
        this.statusCode = statusCode;
        this.response = response;
        this.config = config;
    }
}

export default Api;

4. 使用示例:简单到哭!
const api = new Api('https://api.example.com', { Authorization: 'Bearer token' });

// 添加请求拦截器
api.useRequestInterceptor(async (config, endpoint, method) => {
    console.log(`请求拦截器: ${method} ${endpoint}`);
    return config;
});

// 发起 GET 请求(带缓存)
api.get('/users', { page: 1 }, 'include', 10000, 3, true)
    .then(data => console.log(data))
    .catch(error => console.error(error));

// 发起 POST 请求
api.post('/users', { name: 'John' })
    .then(data => console.log(data))
    .catch(error => console.error(error));

5. 总结:这个工具为什么值得你拥有?
  • 高效:解决网络请求中的各种痛点,提升开发效率。
  • 灵活:支持自定义配置、拦截器、缓存等功能。
  • 稳定:超时、重试、取消等机制,确保请求的稳定性。

如果你觉得这个工具不错,欢迎点赞、转发、打赏!你的支持是我持续分享的动力!


结尾:

好了,今天的分享就到这里。如果你觉得这篇文章对你有帮助,别忘了关注我,我会持续分享更多实用的技术干货!如果你有任何问题或建议,欢迎在评论区留言,我会一一回复!

求关注、求转发、求打赏!你们的支持是我最大的动力!


互动话题:

你在开发中遇到过哪些网络请求的坑?欢迎在评论区分享你的故事!