1.0.4 • Published 4 months ago

fetch-sdk v1.0.4

Weekly downloads
-
License
MIT
Repository
-
Last release
4 months ago

Fetch SDK

一个基于 Fetch API 的现代化 HTTP 客户端,提供简单易用的接口封装,支持请求拦截、流式数据和文件处理。

特性

  • ✨ 优雅的 API 设计,类似 axios 的使用体验
  • 🚀 支持请求和响应拦截器
  • 📦 自动 JSON 数据处理
  • 🔄 支持请求取消
  • 📊 支持上传/下载进度监控
  • 📥 支持流式数据处理
  • 🛡️ 完善的 TypeScript 支持
  • 🔌 支持多实例创建

快速开始

安装

npm install fetch-sdk
# 或
yarn add fetch-sdk

基础使用

import fetchClient from 'fetch-sdk';

// 创建实例(推荐方式)
const service = fetchClient.create({
    baseURL: 'https://api.example.com',
    timeout: 5000,
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'text/event-stream'  // SSE 支持
    },
    withCredentials: true,
    validateStatus: (status) => status >= 200 && status < 500,
    stream: true  // 启用流式处理
});

// 使用实例
try {
    const data = await service.get('/users', {
        params: { page: 1 },
        headers: { 'X-Token': 'xxx' }
    });
    console.log('请求成功:', data);
} catch (error) {
    console.error('请求失败:', error);
}

详细功能

1. 实例配置

创建实例时可配置的选项:

配置项类型默认值说明示例
baseURLstring''请求的基础URL'https://api.example.com'
timeoutnumber-请求超时时间(ms)5000
headersobject{'Content-Type': 'application/json'}默认请求头{ 'X-Token': 'xxx' }
validateStatusfunctionstatus => status >= 200 && status < 300状态码校验status => status < 500
withCredentialsbooleanfalse跨域请求是否带凭证true
responseTypestring'auto'响应数据类型'json'
const service = fetchClient.create({
    baseURL: 'https://api.example.com',
    timeout: 5000,
    headers: {
        'Content-Type': 'application/json',
        'X-Custom-Header': 'value'
    },
    validateStatus: (status) => status < 500,
    withCredentials: true
});

2. 请求方法

支持的请求方法及其使用:

方法参数说明示例
get(url, config)url: string, config?: RequestConfigGET请求service.get('/users', { params: { id: 1 } })
post(url[, data, config])url: string, data?: any, config?: RequestConfigPOST请求service.post('/users', { name: 'John' })
put(url[, data, config])url: string, data?: any, config?: RequestConfigPUT请求service.put('/users/1', { name: 'John' })
delete(url, config)url: string, config?: RequestConfigDELETE请求service.delete('/users/1')
request(url, config)url: string, config?: RequestConfig通用请求方法service.request('/api', { method: 'PATCH' })
// GET 请求示例
const getExample = async () => {
    // 1. 简单请求
    const users = await service.get('/users');

    // 2. 带查询参数
    const user = await service.get('/users', {
        params: { 
            id: 1,
            type: 'detail'
        }
    });

    // 3. 带请求头
    const data = await service.get('/data', {
        headers: {
            'Authorization': 'Bearer token'
        }
    });
};

// POST 请求示例
const postExample = async () => {
    // 1. 发送 JSON 数据
    await service.post('/users', {
        name: 'John',
        age: 30
    });

    // 2. 发送表单数据
    const formData = new FormData();
    formData.append('file', file);
    await service.post('/upload', formData);

    // 3. 发送 URL 编码数据
    const params = new URLSearchParams();
    params.append('name', 'John');
    await service.post('/submit', params);
};

3. 拦截器

拦截器配置及使用:

// 请求拦截器
service.interceptors.request.use(
    config => {
        // 请求前处理
        config.headers['Token'] = getToken();
        return config;
    },
    error => {
        // 请求错误处理
        return Promise.reject(error);
    }
);

// 响应拦截器
service.interceptors.response.use(
    response => {
        // 统一处理响应
        const { code, data, message } = response.data;
        if (code === 0) {
            return data;
        }
        throw new Error(message);
    },
    error => {
        // 错误处理
        if (error.response?.status === 401) {
            // 处理未授权
        }
        return Promise.reject(error);
    }
);

4. 文件处理

文件上传和下载功能:

4.1 基础文件处理

// 1. 文件上传
const uploadFile = async (file) => {
    const formData = new FormData();
    formData.append('file', file);
    
    try {
        await service.post('/upload', formData, {
            headers: {
                'Content-Type': 'multipart/form-data'
            },
            onProgress: ({ loaded, total, progress }) => {
                console.log(`上传进度: ${progress}%`);
            }
        });
    } catch (error) {
        console.error('上传失败:', error);
    }
};

// 2. 文件下载
const downloadFile = async () => {
    try {
        const blob = await service.download('/files/report.pdf', {
            filename: 'report.pdf',
            mimeType: 'application/pdf',
            onProgress: ({ progress }) => {
                console.log(`下载进度: ${progress}%`);
            }
        });
        
        // 如果不想自动下载,可以自行处理 blob
        return blob;
    } catch (error) {
        console.error('下载失败:', error);
    }
};

4.2 断点续传

SDK 提供了文件上传和下载的断点续传功能,支持大文件传输时的断点恢复。

上传断点续传
// 断点续传上传示例
const handleUploadWithResume = async (file) => {
    try {
        await service.uploadWithResume(file, '/api/upload', {
            onProgress: ({ uploaded, total, progress }) => {
                console.log(`上传进度: ${progress}%`);
            }
        });
        console.log('上传完成');
    } catch (error) {
        console.error('上传暂停,已保存断点:', error.message);
        // 稍后可以使用相同的参数重新调用来继续上传
    }
};
下载断点续传
// 断点续传下载示例
const handleDownloadWithResume = async () => {
    try {
        const blob = await service.downloadWithResume('/api/files/large.zip', {
            filename: 'large.zip',
            mimeType: 'application/zip',
            onProgress: ({ downloaded, total, progress }) => {
                console.log(`下载进度: ${progress}%`);
            }
        });
        
        // 下载完成后处理文件
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'large.zip';
        a.click();
        URL.revokeObjectURL(url);
    } catch (error) {
        console.error('下载暂停,已保存断点:', error.message);
        // 稍后可以使用相同的参数重新调用来继续下载
    }
};
特性说明

断点续传功能特点:

  1. 自动分片:

    • 默认分片大小为 1MB
    • 可通过配置自定义分片大小
    • 支持超大文件传输
  2. 进度保存:

    • 自动保存传输进度到 localStorage
    • 断点信息持久化
    • 支持页面刷新后继续传输
  3. 错误处理:

    • 网络错误自动保存断点
    • 支持手动暂停/继续
    • 提供详细的错误信息
  4. 进度监控:

    • 实时进度回调
    • 提供已传输大小和总大小信息
    • 支持进度百分比计算
配置选项
选项类型默认值说明
chunkSizenumber1024 * 1024分片大小(字节)
onProgressfunction-进度回调函数
mimeTypestring'application/octet-stream'文件类型
filenamestring-保存的文件名
服务端配置要求

服务端需要支持以下功能:

  1. 分片上传接口:
// 服务端接收分片示例(Node.js + Express)
app.post('/upload', (req, res) => {
    const uploadId = req.headers['x-upload-id'];
    const chunkIndex = req.headers['x-chunk-index'];
    const totalChunks = req.headers['x-total-chunks'];
    // 处理分片...
});
  1. 断点下载支持:
// 服务端支持断点下载示例
app.get('/download', (req, res) => {
    const range = req.headers.range;
    if (range) {
        // 处理断点下载请求...
        res.status(206);
        res.set('Accept-Ranges', 'bytes');
        // 发送部分内容...
    }
});

5. EventStream 响应处理

Server-Sent Events (SSE) 基本使用示例:

注意: 使用流式处理(stream: true)时,responseType 设置将被忽略,因为流数据始终以文本形式返回。如果需要 JSON 数据,需要手动解析。

const handleEventStream = async () => {
    const stream = await service.request('/events', {
        stream: true,
        headers: { 'Accept': 'text/event-stream' },
        // responseType: 'json', // 注意:此设置在流式处理时不生效
        method: 'POST',
        data: {
            message: 'Hello',
            type: 'chat'
        }
    });

    try {
        while (true) {
            const chunk = await stream.read();
            if (chunk === null) break; // 流结束
            
            // 需要手动解析 JSON 字符串
            try {
                const data = JSON.parse(chunk);
                console.log('收到 JSON 数据:', data);
            } catch (e) {
                console.log('收到普通文本:', chunk);
            }
        }
    } catch (error) {
        console.error('处理错误:', error);
    } finally {
        if (stream?.reader) {
            await stream.reader.cancel();
        }
    }
};

// 使用工具函数处理 JSON 流
const handleJSONStream = async () => {
    const stream = await service.request('/api/chat', {
        stream: true,
        method: 'POST',
        data: { message: 'Hello' }
    });

    const processJSON = (text) => {
        try {
            return JSON.parse(text);
        } catch (e) {
            return text;
        }
    };

    try {
        while (true) {
            const chunk = await stream.read();
            if (chunk === null) break;
            
            const data = processJSON(chunk);
            console.log('处理后的数据:', data);
        }
    } finally {
        if (stream?.reader) {
            await stream.reader.cancel();
        }
    }
};

更多高级用法请参考文档底部的 高级特性 - EventStream 章节。

跨域认证配置

跨域请求时的认证处理配置:

配置项类型默认值说明
withCredentialsbooleanfalse跨域请求时是否携带认证信息(cookies、HTTP认证及客户端SSL证书等)
credentialsstring'same-origin'请求的凭据模式,可选值:'omit'、'same-origin'、'include'
// 1. 使用 withCredentials
const service = fetchClient.create({
    baseURL: 'https://api.example.com',
    withCredentials: true,  // 允许跨域请求携带 cookies
    // ...
});

// 2. 使用 credentials
const service = fetchClient.create({
    baseURL: 'https://api.example.com',
    credentials: 'include',  // 同 withCredentials: true
    // ...
});

注意事项: 1. 当设置 withCredentials: true 时:

  • 服务端必须设置 Access-Control-Allow-Credentials: true
  • 服务端的 Access-Control-Allow-Origin 不能设置为 '*',必须指定具体域名
  • 响应头中的 Set-Cookie 才会被浏览器接受并存储
  1. credentials 可选值说明:

    • 'omit': 从不发送 cookies
    • 'same-origin': 只有当请求同源时才发送 cookies(默认值)
    • 'include': 总是发送 cookies,等同于 withCredentials: true
  2. 安全考虑:

    • 启用此配置会增加 CSRF 攻击风险,建议同时实现 CSRF 令牌机制
    • 仅在确实需要跨域认证时才启用此配置
    • 建议配合 HTTPS 使用,确保数据传输安全
  3. 使用场景:

    • 跨域登录认证
    • 需要访问用户会话数据
    • 多服务之间的认证信息共享
// 完整配置示例
const service = fetchClient.create({
    baseURL: 'https://api.example.com',
    withCredentials: true,
    headers: {
        'Content-Type': 'application/json',
        'X-Requested-With': 'XMLHttpRequest'  // 用于识别 AJAX 请求
    },
    validateStatus: (status) => status >= 200 && status < 500,
});

// 配合服务端设置示例(Node.js + Express)
app.use(cors({
    origin: 'http://localhost:8080',  // 指定允许的源
    credentials: true,  // 允许携带认证信息
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'X-Requested-With']
}));

API 文档

请求方法

方法参数说明示例
get(url, options?)GET 请求service.get('/users', { params: { page: 1 } })
post(url, data?, options?)POST 请求service.post('/users', { name: 'John' })
request(url, options?)自定义请求service.request('/api', { method: 'PUT' })

请求配置

选项类型默认值说明示例
methodstring'GET'请求方法{ method: 'POST' }
headersobject{'Content-Type': 'application/json'}自定义请求头{ headers: { 'X-Token': 'xxx' } }
paramsobject-URL 查询参数{ params: { id: 1 } }
dataany-请求体数据{ data: { name: 'test' } }
timeoutnumber30000请求超时时间(ms){ timeout: 5000 }
responseTypestring'auto'响应数据类型('json'/'text'/'blob'/'arrayBuffer'/'formData'/'auto'){ responseType: 'json' }
signalAbortSignal-用于取消请求的信号{ signal: controller.signal }
streambooleanfalse以流式方式处理响应{ stream: true }
withCredentialsbooleanfalse是否携带凭证{ withCredentials: true }
validateStatusfunctionstatus => status >= 200 && status < 300响应状态码校验{ validateStatus: status => status < 500 }

stream 配置详解

stream 配置用于处理流式响应数据,主要用于以下场景:

  1. Server-Sent Events (SSE) 接收实时更新
  2. 大文件下载时分块处理
  3. 流式 API 响应(如 ChatGPT 流式响应)

使用示例:

// 1. 基础流式处理
const stream = await service.request('/stream-api', { 
    stream: true,
    headers: {
        'Accept': 'text/event-stream'
    }
});

// 使用 iterateLines 按行读取(适用于 SSE)
for await (const line of stream.iterateLines()) {
    if (line.startsWith('data: ')) {
        const data = JSON.parse(line.slice(6));
        console.log('收到数据:', data);
    }
}

// 2. 带进度监控的流式处理
const stream = await service.request('/large-file', { 
    stream: true,
    onProgress: ({ loaded, total, progress }) => {
        if (total) {
            console.log(`接收进度: ${progress}%`);
        } else {
            console.log(`已接收: ${loaded} bytes`);
        }
    }
});

// 使用 read 方法逐块读取数据
let chunk;
while ((chunk = await stream.read()) !== null) {
    console.log('收到数据块:', chunk);
}

// 3. 资源清理示例
let stream;
try {
    stream = await service.request('/stream', { stream: true });
    
    for await (const line of stream.iterateLines()) {
        console.log('接收到数据:', line);
    }
} finally {
    // 确保释放资源
    if (stream?.reader) {
        await stream.reader.cancel();
    }
}

流式响应对象的属性和方法:

属性/方法类型说明
readasync function读取下一个数据块,返回 null 表示数据已读取完毕
iterateLinesasync iterator按行迭代数据,适合处理文本流和 SSE
totalnumber总数据大小(仅当服务器提供 content-length 时可用)
loadednumber当前已接收的数据大小
readerReadableStreamDefaultReader底层读取器实例,用于手动控制和资源清理

注意事项:

  1. 使用 read() 方法时:

    • 返回 null 表示数据流结束
    • 返回的是解码后的文本数据
    • 自动更新 loaded 属性和触发进度回调
  2. 使用 iterateLines() 时:

    • 自动处理行分割
    • 适合处理 SSE 和文本流
    • 会自动处理编码
  3. 资源管理:

    • 务必在 finally 中调用 reader.cancel()
    • 避免资源泄露
    • 支持提前中断数据流

错误处理

SDK 使用标准化的错误对象,包含以下属性:

try {
    await service.get('/api');
} catch (error) {
    // error.config - 请求配置信息
    // error.request - 请求实例
    // error.response - 响应对象(如果存在)
    // error.status - HTTP状态码(如果存在)
    // error.statusText - 状态描述(如果存在)
    console.log(error.message); // 错误消息
}

请求取消

使用标准的 AbortController 来取消请求:

const controller = new AbortController();

service.get('/api/data', {
    signal: controller.signal 
}).catch(error => {
    if (error.name === 'AbortError') {
        console.log('请求已被取消');
    }
});

// 取消请求
controller.abort();

实际应用场景

  1. 搜索场景下取消上一次请求:
let controller = null;

const handleSearch = async (keyword) => {
    // 取消之前的请求
    if (controller) {
        controller.abort();
    }
    
    // 创建新的 controller
    controller = new AbortController();
    
    try {
        const result = await service.get('/search', {
            params: { keyword },
            signal: controller.signal
        });
        return result;
    } catch (error) {
        if (error.name !== 'AbortError') {
            throw error;
        }
    }
};
  1. 组件卸载时取消请求:
import React, { useEffect } from 'react';

function DataComponent() {
    useEffect(() => {
        const controller = new AbortController();
        
        const fetchData = async () => {
            try {
                const data = await service.get('/api/data', {
                    signal: controller.signal
                });
                // 处理数据
            } catch (error) {
                if (error.name !== 'AbortError') {
                    console.error('获取数据失败:', error);
                }
            }
        };
        
        fetchData();
        
        // 组件卸载时取消请求
        return () => controller.abort();
    }, []);
    
    return <div>...</div>;
}
  1. 超时和取消的结合:
const timeoutRequest = async (url, timeout = 5000) => {
    const controller = new AbortController();
    
    try {
        const response = await service.get(url, {
            signal: controller.signal,
            timeout
        });
        return response;
    } catch (error) {
        if (error.name === 'AbortError') {
            throw new Error('请求超时或被取消');
        }
        throw error;
    }
};

最佳实践

请求取消示例

// 1. 引入CancelToken
import fetchClient, { CancelToken } from 'fetch-sdk';
// 2. 创建AbortController
const controller = new AbortController();

// 3. 发起可取消请求
const getUserRequest = service.get('/users', {
  signal: controller.signal
}).catch(error => {
  if (error.name === 'AbortError') {
    console.log('请求已被取消');
  }
});

// 4. 取消请求
controller.abort();

// 5. 复用signal(可选)
const getPostsRequest = service.get('/posts', {
  signal: controller.signal // 使用同一个signal
});
  1. 通用配置集中管理
// api.js
const client = new FetchClient('https://api.example.com', {
    timeout: 5000,
    validateStatus: status => status < 500,
    headers: {
        'Accept': 'application/json',
        'X-Client-Version': '1.0.0'
    }
});

// 统一错误处理
client.addResponseInterceptor(
    response => response,
    error => {
        handleApiError(error);
        return Promise.reject(error);
    }
);

export default client;
  1. 业务模块封装
// userApi.js
import client from './api';

export const userApi = {
    getProfile: () => client.get('/user/profile'),
    updateProfile: (data) => client.post('/user/profile', data),
    uploadAvatar: (file) => {
        const formData = new FormData();
        formData.append('avatar', file);
        return client.post('/user/avatar', formData);
    }
};
  1. 请求取消处理
// 搜索场景
let searchCancel;

const search = async (keyword) => {
    // 取消上一次请求
    if (searchCancel) {
        searchCancel('新搜索请求发起');
    }
    
    const { token, cancel } = CancelToken.source();
    searchCancel = cancel;
    
    try {
        const result = await service.get('/search', {
            params: { keyword },
            cancelToken: token
        });
        return result;
    } catch (error) {
        if (!isCancel(error)) {
            throw error;
        }
    }
};
  1. 流式数据优雅处理
const handleStreamWithCleanup = async () => {
    let stream;
    try {
        stream = await service.request('/stream', { stream: true });
        for await (const line of stream.iterateLines()) {
            processLine(line);
        }
    } catch (error) {
        console.error('Stream error:', error);
    } finally {
        // 确保资源释放
        if (stream?.reader) {
            await stream.reader.cancel();
        }
    }
};

高级特性

EventStream 详细说明

  1. 作用说明:

    • 告知服务器客户端期望接收 Server-Sent Events (SSE) 格式的数据流
    • 服务器根据此头部判断是否使用 SSE 协议发送数据
    • 建立长连接,保持数据流的持续推送
  2. 是否必需:

    • 不是强制必需的,但强烈建议设置
    • 不设置可能导致的问题:
      • 服务器可能返回普通 HTTP 响应而不是事件流
      • 某些服务器会拒绝不带正确 Accept 头的 SSE 请求
      • 无法享受浏览器对 SSE 的原生优化(如自动重连)
  3. 使用场景:

// 推荐的完整配置
const service = fetchClient.create({
    headers: {
        'Accept': 'text/event-stream',  // 声明接收 SSE
        'Cache-Control': 'no-cache',    // 禁用缓存
        'Connection': 'keep-alive'      // 保持连接
    }
});

// 最小配置(不推荐)
const service = fetchClient.create({
    // 不设置 Accept 头,依赖服务器默认行为
});
  1. 服务器端对应配置:
// Node.js Express 示例
app.get('/events', (req, res) => {
    // 检查客户端是否支持 SSE
    if (req.headers.accept && req.headers.accept.includes('text/event-stream')) {
        res.setHeader('Content-Type', 'text/event-stream');
        res.setHeader('Cache-Control', 'no-cache');
        res.setHeader('Connection', 'keep-alive');
        
        // 发送事件流
        const sendEvent = () => {
            res.write(`data: ${JSON.stringify({ time: new Date() })}\n\n`);
        };
        
        const timer = setInterval(sendEvent, 1000);
        
        req.on('close', () => clearInterval(timer));
    } else {
        // 客户端不支持 SSE,返回普通响应
        res.json({ error: 'SSE not supported' });
    }
});
  1. 调试技巧:

    • 使用浏览器开发者工具查看 Network 面板
    • 确认请求头中包含 Accept: text/event-stream
    • 检查响应头中的 Content-Type: text/event-stream
    • 观察连接是否保持打开状态
  2. 常见问题处理:

    // 处理连接断开
    let retryCount = 0;
    const maxRetries = 3;
    
    const connectSSE = async () => {
        try {
            const stream = await service.request('/events', {
                stream: true,
                headers: {
                    'Accept': 'text/event-stream'
                }
            });
            
            retryCount = 0; // 重置重试计数
            
            for await (const line of stream.iterateLines()) {
                processEventData(line);
            }
        } catch (error) {
            if (retryCount < maxRetries) {
                retryCount++;
                console.log(`连接断开,${retryCount}秒后重试...`);
                setTimeout(connectSSE, retryCount * 1000);
            } else {
                console.error('多次重试失败,放弃连接');
            }
        }
    };
  3. 性能考虑:

    • SSE 连接会占用服务器资源,建议设置最大连接数
    • 考虑在不需要时及时关闭连接
    • 可以使用心跳机制检测连接状态
    // 心跳检测示例
    let lastEventTime = Date.now();
    const heartbeatInterval = setInterval(() => {
        const now = Date.now();
        if (now - lastEventTime > 30000) { // 30秒无数据
            controller.abort(); // 断开连接
            clearInterval(heartbeatInterval);
            connectSSE(); // 重新连接
        }
    }, 5000);

高级用法示例

1. EventStream 高级处理

1.1 使用 iterateLines 处理 SSE:

const handleSSEWithLines = async () => {
    const stream = await service.request('/events', {
        stream: true,
        headers: { 'Accept': 'text/event-stream' }
    });

    try {
        for await (const line of stream.iterateLines()) {
            if (line.startsWith('data: ')) {
                console.log('收到数据:', JSON.parse(line.slice(6)));
            }
        }
    } catch (error) {
        console.error('处理错误:', error);
    }
};

1.2 带缓冲的数据处理:

const handleStreamWithBuffer = async () => {
    const stream = await service.request('/events', {
        stream: true,
        headers: { 'Accept': 'text/event-stream' }
    });

    try {
        let buffer = '';
        while (true) {
            const chunk = await stream.read();
            if (chunk === null) break;

            buffer += chunk;
            const lines = buffer.split('\n');
            buffer = lines.pop() || '';
            
            for (const line of lines) {
                if (line.startsWith('data: ')) {
                    console.log('处理数据:', JSON.parse(line.slice(6)));
                }
            }
        }
    } catch (error) {
        console.error('处理错误:', error);
    }
};

1.3 带超时和重试的事件流处理:

const handleStreamWithTimeout = async () => {
    const stream = await service.request('/events', {
        stream: true,
        headers: { 'Accept': 'text/event-stream' }
    });

    try {
        while (true) {
            const timeoutPromise = new Promise((_, reject) => {
                setTimeout(() => reject(new Error('读取超时')), 5000);
            });

            const chunk = await Promise.race([
                stream.read(),
                timeoutPromise
            ]);

            if (chunk === null) break;
            console.log('收到数据:', chunk);
        }
    } catch (error) {
        if (error.message === '读取超时') {
            console.log('准备重试...');
        }
    }
};

2. 请求重试机制

const request = async (url, options = {}, retries = 3) => {
    for (let i = 0; i < retries; i++) {
        try {
            return await service.request(url, options);
        } catch (error) {
            if (i === retries - 1) throw error;
            await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        }
    }
};

3. 批量请求处理

const batchRequest = async (urls, concurrency = 3) => {
    const results = [];
    const queue = [...urls];
    
    const workers = Array(concurrency).fill().map(async () => {
        while (queue.length) {
            const url = queue.shift();
            const result = await service.get(url);
            results.push(result);
        }
    });

    await Promise.all(workers);
    return results;
};

4. 智能缓存

const cacheMap = new Map();

const cachedRequest = async (url, options = {}, ttl = 60000) => {
    const key = `${url}-${JSON.stringify(options)}`;
    const cached = cacheMap.get(key);
    
    if (cached && Date.now() - cached.timestamp < ttl) {
        return cached.data;
    }

    const data = await service.request(url, options);
    cacheMap.set(key, { data, timestamp: Date.now() });
    return data;
};

5. WebSocket 降级方案

class RealTimeClient {
    constructor(url) {
        this.url = url;
        this.fallbackToSSE = false;
    }

    async connect() {
        try {
            if (!this.fallbackToSSE) {
                // 尝试 WebSocket
                this.ws = new WebSocket(this.url);
            } else {
                // 降级到 SSE
                const stream = await service.request(this.url, {
                    stream: true,
                    headers: { 'Accept': 'text/event-stream' }
                });
                this.handleSSE(stream);
            }
        } catch (error) {
            this.fallbackToSSE = true;
            this.connect();
        }
    }
}

6. 文件上传断点续传

const uploadWithResume = async (file, chunkSize = 1024 * 1024) => {
    const chunks = Math.ceil(file.size / chunkSize);
    let uploaded = 0;

    for (let i = 0; i < chunks; i++) {
        const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
        const formData = new FormData();
        formData.append('chunk', chunk);
        formData.append('index', i);

        await service.post('/upload', formData, {
            headers: {
                'X-Upload-Id': uploadId,
                'X-Chunk-Index': i,
                'X-Total-Chunks': chunks
            }
        });
        
        uploaded += chunk.size;
        console.log(`上传进度: ${Math.round((uploaded / file.size) * 100)}%`);
    }
};