如何在JavaScript中实现C# JsonSerializer.DeserializeAsyncEnumerable等效功能?
在JavaScript中实现类似C# JsonSerializer.DeserializeAsyncEnumerable的功能
当.NET控制器返回IAsyncEnumerable时,低速yield会和JS的response.body.getReader().read()一一对应,但全速运行时数据会被缓冲成JSON块。要实现和C# JsonSerializer.DeserializeAsyncEnumerable一样的功能,核心是流式解析JSON序列,逐个提取元素,而非等待整个响应完成再解析。
核心实现思路
- 逐块读取响应流,维护缓冲区存储未完成的JSON片段
- 每次读取新块后,尝试从缓冲区解析完整的JSON元素
- 解析出的元素逐个返回(类似C#的yield),剩余未解析片段留在缓冲区等待下一块数据
- 流结束后处理最后剩余的缓冲区内容
代码实现
async function* deserializeAsyncEnumerable(response) { const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; // 匹配JSON对象结尾,兼容带逗号/不带逗号的格式(适配.NET流式输出) const itemPattern = /(\{[^{}]*\})\s*,?\s*/g; // 处理JSON数组的首尾包裹符 function trimArrayWrapper() { buffer = buffer.trim(); if (buffer.startsWith('[')) buffer = buffer.slice(1).trim(); if (buffer.endsWith(']')) buffer = buffer.slice(0, -1).trim(); } while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); trimArrayWrapper(); let match; // 循环提取所有完整的JSON元素 while ((match = itemPattern.exec(buffer)) !== null) { try { const item = JSON.parse(match[1]); yield item; } catch (err) { // 解析失败说明片段不完整,回退索引并跳出循环 itemPattern.lastIndex = match.index; break; } } // 更新缓冲区,保留未解析的部分 buffer = buffer.slice(itemPattern.lastIndex); } // 处理最后剩余的内容 trimArrayWrapper(); if (buffer) { try { const item = JSON.parse(buffer); yield item; } catch (err) { console.error('Failed to parse final JSON fragment:', err); } } }
使用示例
async function processStreamedData() { const response = await fetch('/your-api-endpoint'); if (!response.ok) { throw new Error(`Request failed: ${response.status}`); } // 逐个处理流式返回的元素 for await (const dataItem of deserializeAsyncEnumerable(response)) { console.log('Received item:', dataItem); // 这里添加业务逻辑,比如渲染页面、存储数据等 } } // 执行流式处理 processStreamedData().catch(err => console.error(err));
关键细节说明
- 缓冲区与分块解析:服务器全速发送时数据会合并成大块,甚至单个JSON元素可能拆分在多个块中,缓冲区用来拼接片段,确保仅解析完整元素
- 正则匹配调整:如果接口返回换行分隔的JSON(NDJSON),可将正则改为
/^.*$/gm简化处理 - TextDecoder的stream模式:开启
{ stream: true }避免多字节字符被截断导致解码错误 - 数组包裹处理:默认适配.NET
IAsyncEnumerable返回的[{},{},...]格式,若接口返回无包裹的单个对象,可删除trimArrayWrapper相关逻辑
内容的提问来源于stack exchange,提问作者McMurphy
相关产品推荐
相关产品推荐

