Back to list网络请求与通信机制
Site Owner
Published on 2026-05-21
系统讲解XHR完整状态机、Fetch与Promise集成、Response流式读取、AbortController取消机制及实时通信方案对比
网络请求与通信机制
现代 Web 应用的网络通信远不止简单的 HTTP 请求。从 Fetch API 的流式响应、AbortController 取消机制,到 WebSocket 全双工通信、Server-Sent Events 推送、WebRTC 点对点连接,本文全面覆盖 JavaScript 网络通信的底层机制与最佳实践。
目录
- Fetch API 深度用法
- 请求拦截与中间件
- 流式响应处理
- WebSocket 全双工通信
- Server-Sent Events
- 请求取消与竞态处理
- 实战案例
- 深度追问
- 总结表格
1. Fetch API 深度用法
const response = await fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
: .({ : }),
: .(),
: ,
: ,
: ,
:
});
(!response.) ();
data = response.();
() {
totalSize = file.;
uploadedSize = ;
stream = file.().( ({
() {
uploadedSize += chunk.;
(uploadedSize / totalSize);
controller.(chunk);
}
}));
(url, {
: ,
: stream,
: ,
: { : file. }
});
}
JavaScript网络请求全解—从XHR到Fetch、Streams与WebSocket|新宇宙博客body
JSON
stringify
key
'value'
signal
AbortSignal
timeout
5000
cache
'no-cache'
credentials
'include'
keepalive
true
priority
'high'
if
ok
throw
new
Error
`HTTP ${response.status}`
const
await
json
async
function
uploadWithProgress
url, file, onProgress
const
size
let
0
const
stream
pipeThrough
new
TransformStream
transform
chunk, controller
byteLength
onProgress
enqueue
return
fetch
method
'PUT'
body
duplex
'half'
headers
'Content-Type'
type
2. 请求拦截与中间件
class HttpClient {
#middlewares = [];
#baseUrl;
constructor(baseUrl) {
this.#baseUrl = baseUrl;
}
use(middleware) {
this.#middlewares.push(middleware);
return this;
}
async request(path, options = {}) {
const url = new URL(path, this.#baseUrl).href;
let config = { url, ...options };
const chain = this.#middlewares.reduceRight(
(next, middleware) => (cfg) => middleware(cfg, next),
(cfg) => fetch(cfg.url, cfg).then(this.#handleResponse)
);
return chain(config);
}
#handleResponse(response) {
if (!response.ok) {
const error = new Error(`HTTP ${response.status}`);
error.response = response;
throw error;
}
const contentType = response.headers.get('content-type');
return contentType?.includes('json') ? response.json() : response.text();
}
}
const authMiddleware = async (config, next) => {
config.headers = { ...config.headers, Authorization: `Bearer ${getToken()}` };
return next(config);
};
const retryMiddleware = async (config, next) => {
for (let i = 0; i < 3; i++) {
try { return await next(config); }
catch (e) { if (i === 2) throw e; await sleep(1000 * (i + 1)); }
}
};
const client = new HttpClient('https://api.example.com')
.use(authMiddleware)
.use(retryMiddleware);
3. 流式响应处理
async function* streamJSON(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (line.trim()) {
yield JSON.parse(line);
}
}
}
if (buffer.trim()) yield JSON.parse(buffer);
}
for await (const chunk of streamJSON('/api/chat/stream')) {
appendToUI(chunk.text);
}
4. WebSocket 全双工通信
class RobustWebSocket {
#url; #ws; #queue = [];
#reconnecting = false;
#handlers = new Map();
#heartbeatInterval;
constructor(url, options = {}) {
this.#url = url;
this.maxReconnectDelay = options.maxReconnectDelay || 30000;
this.connect();
}
connect() {
this.#ws = new WebSocket(this.#url);
this.#ws.onopen = () => {
this.#reconnecting = false;
this.#flushQueue();
this.#startHeartbeat();
};
this.#ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'pong') return;
this.#handlers.get(msg.type)?.forEach(h => h(msg.data));
};
this.#ws.onclose = (e) => {
this.#stopHeartbeat();
if (!e.wasClean) this.#reconnect();
};
}
send(type, data) {
const message = JSON.stringify({ type, data });
if (this.#ws.readyState === WebSocket.OPEN) {
this.#ws.send(message);
} else {
this.#queue.push(message);
}
}
on(type, handler) {
if (!this.#handlers.has(type)) this.#handlers.set(type, new Set());
this.#handlers.get(type).add(handler);
return () => this.#handlers.get(type).delete(handler);
}
#startHeartbeat() {
this.#heartbeatInterval = setInterval(() => {
this.send('ping', {});
}, 30000);
}
#stopHeartbeat() { clearInterval(this.#heartbeatInterval); }
#flushQueue() { while (this.#queue.length) this.#ws.send(this.#queue.shift()); }
#reconnect() {
if (this.#reconnecting) return;
this.#reconnecting = true;
const delay = Math.min(1000 * Math.pow(2, Math.random() * 5), this.maxReconnectDelay);
setTimeout(() => this.connect(), delay);
}
}
5. Server-Sent Events
function createEventSource(url, handlers) {
const source = new EventSource(url);
source.onopen = () => handlers.onConnect?.();
source.onerror = () => handlers.onError?.();
source.addEventListener('message', e => handlers.onMessage?.(JSON.parse(e.data)));
source.addEventListener('notification', e => handlers.onNotification?.(JSON.parse(e.data)));
return {
close: () => source.close(),
get readyState() { return source.readyState; }
};
}
6. 请求取消与竞态处理
function useLatest() {
let currentId = 0;
return async function fetchLatest(url) {
const id = ++currentId;
const response = await fetch(url);
if (id !== currentId) {
throw new Error('Stale request');
}
return response.json();
};
}
function createAbortGroup() {
const controllers = new Set();
return {
create() {
const controller = new AbortController();
controllers.add(controller);
controller.signal.addEventListener('abort', () => controllers.delete(controller));
return controller;
},
abortAll() {
controllers.forEach(c => c.abort());
controllers.clear();
}
};
}
7. 实战案例
实战案例 1:API 客户端(完整版)
class APIClient {
#baseUrl; #token; #abortGroup;
constructor(baseUrl) {
this.#baseUrl = baseUrl;
this.#abortGroup = createAbortGroup();
}
setToken(token) { this.#token = token; }
async get(path, params) {
const url = new URL(path, this.#baseUrl);
if (params) Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
return this.#request(url, { method: 'GET' });
}
async post(path, body) {
return this.#request(new URL(path, this.#baseUrl), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
}
async #request(url, options) {
const controller = this.#abortGroup.create();
const response = await fetch(url, {
...options,
signal: controller.signal,
headers: { ...options.headers, ...(this.#token && { Authorization: `Bearer ${this.#token}` }) }
});
if (!response.ok) throw Object.assign(new Error(`HTTP ${response.status}`), { status: response.status });
return response.json();
}
cancelAll() { this.#abortGroup.abortAll(); }
}
实战案例 2:文件分片上传
async function chunkedUpload(file, url, chunkSize = 5 * 1024 * 1024) {
const totalChunks = Math.ceil(file.size / chunkSize);
const uploadId = crypto.randomUUID();
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
await fetch(`${url}/upload`, {
method: 'PUT',
headers: {
'X-Upload-Id': uploadId,
'X-Chunk-Index': i,
'X-Total-Chunks': totalChunks,
'Content-Range': `bytes ${start}-${end - 1}/${file.size}`
},
body: chunk
});
}
await fetch(`${url}/complete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ uploadId, filename: file.name })
});
}
实战案例 3:长轮询降级方案
async function longPoll(url, handler, options = {}) {
const { signal, retryDelay = 1000 } = options;
while (!signal?.aborted) {
try {
const response = await fetch(url, { signal });
if (response.status === 204) continue;
const data = await response.json();
handler(data);
} catch (e) {
if (e.name === 'AbortError') break;
await new Promise(r => setTimeout(r, retryDelay));
}
}
}
8. 深度追问
Q1:fetch 为什么不能原生支持上传进度?
Fetch 的 body 是 ReadableStream,浏览器无法预知流的总大小。XMLHttpRequest 的 upload.onprogress 依赖于 Content-Length 头和底层 socket 写入回调。可以通过 TransformStream 包装实现类似效果。
Q2:HTTP/2 如何影响前端并发请求策略?
HTTP/2 多路复用消除了 HTTP/1.1 的队头阻塞(Head-of-Line Blocking)和每域 6 连接限制。域名分片(domain sharding)不再必要,甚至有害(增加 TLS 握手)。但单个连接仍有流数量限制(通常 100-256)。
Q3:keepalive: true 在页面卸载时有什么限制?
keepalive 请求的 body 大小限制为 64KB。它会在 visibilitychange 或 pagehide 事件后继续发送,适合发送分析数据。但不保证服务器一定能收到响应。
9. 总结表格
| 通信方式 | 方向 | 协议 | 适用场景 |
|---|
| Fetch | 客户端→服务器 | HTTP | REST API |
| WebSocket | 双向 | WS | 实时通信 |
| SSE | 服务器→客户端 | HTTP | 推送通知 |
| WebRTC | 点对点 | UDP/SCTP | 音视频/数据 |
| Beacon | 客户端→服务器 | HTTP | 页面卸载时发送 |