直接用 JSON.parse 解析几百 MB 的 JSON 文件会阻塞主线程、爆内存、导致页面卡死甚至崩溃;异步生成器配合流式读取和分块解析可实现边读边解、按需消费,避免全量加载和 UI 锁定。

直接用 JSON.parse 解析几百 MB 的 JSON 文件,会阻塞主线程、爆内存、导致页面卡死甚至崩溃。异步生成器(async function*)配合流式读取和分块解析,能真正实现“边读边解、按需消费”,把大 JSON 拆成小块逐步处理,不占满内存,也不锁住 UI。
核心思路:不加载全量字符串,改用可读流 + 分块解析
浏览器中无法直接流式解析 JSON 字符串,但可以借助 ReadableStream 和自定义解析逻辑,将 JSON 数据按结构边界(如数组项)切片。关键不是“解析整个 JSON”,而是识别并逐个 yield 解析好的子对象。
- 用
fetch()获取响应体的response.body(返回ReadableStream) - 用
TextDecoderStream或手动分块读取字节流,避免一次性转成巨型字符串 - 在异步生成器中维护一个 JSON 解析上下文(如括号计数、引号状态),识别每个完整 JSON 值的起止位置
- 每识别出一个合法 JSON 值(例如数组里的一个对象),就
yield它的解析结果(已调用JSON.parse的小对象)
简易可行的异步生成器示例(处理 JSON 数组)
假设服务端返回的是标准 JSON 数组格式:[{"id":1,...},{"id":2,...},...]。这是最易分块的场景:
async function* parseJsonArrayStream(readableStream) {
const reader = readableStream.getReader();
const decoder = new TextDecoder();
let buffer = '';
<p>try {
while (true) {
const { done, value } = await reader.read();
if (done) break;</p><pre class="brush:php;toolbar:false;"> buffer += decoder.decode(value, { stream: true });
// 简单策略:找完整的 JSON 对象(基于花括号匹配)
let start = 0;
let depth = 0;
for (let i = 0; i < buffer.length; i++) {
const c = buffer[i];
if (c === '{' && (i === 0 || buffer[i-1] !== '\')) depth++;
if (c === '}' && (i === 0 || buffer[i-1] !== '\')) depth--;
if (depth === 0 && c === '}') {
const itemStr = buffer.slice(start, i + 1).trim();
if (itemStr) {
try {
yield JSON.parse(itemStr); // 只解析这一小段
} catch (e) {
console.warn('跳过无效 JSON 片段:', itemStr);
}
}
start = i + 1;
}
}
buffer = buffer.slice(start);
}} finally { reader.releaseLock(); } }
这个生成器不会等待全部数据到达,只要缓冲区里凑够一个完整对象,就立刻 yield 出来。
立即学习“Java免费学习笔记(深入)”;
配合 for await…of 实现低压力消费
消费者代码写起来非常干净,像写同步代码一样自然:
async function handleLargeJson() {
const response = await fetch('/api/huge-data.json');
const stream = parseJsonArrayStream(response.body);
<p>let count = 0;
for await (const item of stream) {
// 每次只拿到一个已解析的对象,内存占用极小
processItem(item); // 例如渲染一行、存入 IndexedDB、做校验等</p><pre class="brush:php;toolbar:false;">count++;
if (count % 100 === 0) {
// 每处理 100 条主动让出主线程,防卡顿
await new Promise(resolve => setTimeout(resolve, 0));
}} }
注意这里加了 await new Promise(...) 的微任务让点,确保浏览器有空隙响应用户操作,这是前端流式处理的关键细节。
生产环境建议补充的健壮性措施
- 错误恢复:遇到非法 JSON 不 throw,而是跳过并记录日志,继续后续解析
-
资源清理:在生成器末尾或异常时调用
reader.cancel(),防止流泄漏 -
类型预检:先检查响应头
Content-Type: application/json,避免误处理二进制 -
背压控制:如果消费速度慢于生产,可在生成器内部加
if (pendingCount > 10) await sleep(1)暂停读取 - 替代方案参考:对更复杂 JSON(嵌套深、非数组根节点),可集成 stream-json 这类专业流式解析库,它提供事件驱动的 parser、matcher、streamer 组合能力


















