1. 微前端容器标准化的背景与挑战
在大型前端应用架构中,微前端已成为解决单体应用臃肿问题的有效方案。但当我们把不同技术栈的子应用集成到同一个容器时,公共能力的标准化就成为了关键痛点。特别是在网络请求这个基础能力上,每个子应用可能使用不同的请求库(axios/fetch/umi-request),甚至存在重复的拦截器逻辑和认证处理。
我曾参与过一个金融类中台项目,初期各业务团队独立开发的子应用存在以下典型问题:
- 每个子应用都实现了自己的token刷新逻辑
- 错误处理方式不统一(有的用通知组件,有的用控制台日志)
- 监控埋点重复实现
- 请求取消机制缺失
这些问题直接导致了:
- 用户token过期时可能收到多个刷新请求
- 错误提示样式五花八门
- 监控数据重复上报
- 页面切换时未完成的请求不会自动取消
2. 通用请求模块的设计原则
2.1 核心设计目标
在设计通用请求模块时,我们确立了三个核心原则:
- 无侵入性:子应用无需修改现有请求代码
- 能力可插拔:各功能模块(如错误处理、监控)可独立启用
- 渐进式适配:允许子应用逐步迁移到统一方案
2.2 技术架构设计
我们采用了分层架构设计:
┌─────────────────────────────────┐ │ 子应用调用层 │ ├─────────────────────────────────┤ │ 请求代理层(统一入口/出口拦截) │ ├─────────────────────────────────┤ │ 核心功能层(认证/监控/错误处理) │ ├─────────────────────────────────┤ │ 基础适配层 │ │ (fetch/axios/umi-request适配) │ └─────────────────────────────────┘3. 关键实现技术细节
3.1 请求代理的实现
通过劫持全局fetch和XMLHttpRequest实现无侵入接入:
// 保存原生方法 const nativeFetch = window.fetch; const nativeXHR = window.XMLHttpRequest; // 重写fetch window.fetch = async function(...args) { const startTime = Date.now(); try { // 前置处理(添加token/埋点等) const processedArgs = preProcess(args); const response = await nativeFetch.apply(this, processedArgs); // 后置处理(错误码解析/监控上报等) return postProcess(response, startTime); } catch (error) { errorHandler(error); throw error; } }; // XMLHttpRequest代理类似实现...3.2 多实例管理
为每个子应用创建独立的请求实例:
class RequestManager { constructor(appId) { this.appId = appId; this.pendingRequests = new Map(); } addRequest(requestId, controller) { this.pendingRequests.set(requestId, controller); } cancelRequest(requestId) { const controller = this.pendingRequests.get(requestId); if (controller) controller.abort(); } clearAll() { this.pendingRequests.forEach(controller => controller.abort()); this.pendingRequests.clear(); } }3.3 核心功能实现
3.3.1 认证处理
async function handleAuth(response) { if (response.status === 401) { if (!isRefreshing) { isRefreshing = true; try { const newToken = await refreshToken(); updateAllRequestsToken(newToken); return retryOriginalRequest(response.config); } finally { isRefreshing = false; } } else { return waitForTokenRefresh().then(() => retryOriginalRequest(response.config)); } } return response; }3.3.2 错误统一处理
function normalizeError(error) { const errorTypes = { AbortError: { level: 'warn', message: '请求已取消' }, TimeoutError: { level: 'error', message: '请求超时' }, NetworkError: { level: 'error', message: '网络异常' }, default: { level: 'error', message: '系统异常' } }; const errorInfo = errorTypes[error.name] || errorTypes.default; return { ...errorInfo, originalError: error, timestamp: Date.now() }; }4. 性能优化实践
4.1 请求合并策略
对于高频的配置类请求,实现自动合并:
const pendingRequests = new Map(); async function batchRequest(url, params) { const requestKey = `${url}_${JSON.stringify(params)}`; if (pendingRequests.has(requestKey)) { return pendingRequests.get(requestKey); } const promise = fetch(url, params).finally(() => { pendingRequests.delete(requestKey); }); pendingRequests.set(requestKey, promise); return promise; }4.2 缓存策略实现
const cache = new Map(); async function cachedFetch(url, options = {}) { const cacheKey = generateCacheKey(url, options); const cacheTTL = options.cacheTTL || 30000; if (cache.has(cacheKey)) { const { timestamp, data } = cache.get(cacheKey); if (Date.now() - timestamp < cacheTTL) { return data.clone(); } } const response = await fetch(url, options); cache.set(cacheKey, { timestamp: Date.now(), data: response.clone() }); return response; }5. 监控与调试方案
5.1 监控指标设计
我们在请求生命周期中收集以下指标:
const metrics = { startTime: 0, dnsLookup: 0, tcpConnect: 0, sslHandshake: 0, firstByte: 0, download: 0, total: 0, success: false, statusCode: 0, requestSize: 0, responseSize: 0 };5.2 调试工具实现
开发专用的请求调试面板:
class RequestDebugger { constructor() { this.requests = []; window.__requestDebugger = this; } log(request) { this.requests.push(request); if (this.requests.length > 100) { this.requests.shift(); } this.updateDevTools(); } updateDevTools() { // 与浏览器插件通信更新数据 } }6. 落地实践中的经验总结
6.1 渐进式迁移方案
我们设计了三种迁移模式:
- 观察模式:只监控不干预
- 混合模式:部分功能接管
- 完全接管模式
6.2 性能影响评估
经过压测对比,完整方案带来的额外开销:
- 内存占用增加约3-5MB
- 请求延迟增加2-8ms(90分位)
- CPU负载增加约1-2%
6.3 典型问题排查
问题现象:某些子应用请求偶尔失败
排查过程:
- 发现失败请求都来自同一个老版本子应用
- 该子应用使用了非标准的Content-Type头
- 容器层的请求标准化处理未考虑这种case
解决方案:
function normalizeHeaders(headers) { // 保留原有headers的兼容处理 if (headers['content-type']) { headers['Content-Type'] = headers['content-type']; delete headers['content-type']; } return headers; }7. 未来演进方向
当前架构已在以下方面进行优化:
- 增加WebSocket的标准化支持
- 实验性的Server-Sent Events集成
- 基于WASM的高性能序列化方案
- 智能降级策略(弱网环境自动切换协议)
在大型电商项目中,该方案已支撑日均3亿+请求量,子应用接入成本降低70%,异常请求处理效率提升40%。后续计划开源核心实现,与社区共同完善微前端标准化生态。