Node 20 里用 undici 的 fetch 读 SSE,response.body 通过 getReader 逐块解码,长回答的最后十几个字经常缺失,短回答正常,换成 axios 就完整。
一开始怀疑是「读循环在 done 之前就 break,剩余数据被丢弃」,查了一圈,方向不对。
原因不止一个,得分开看。循环里最后一次 buffer 没有 flush。也可能是TextDecoder 没有传 stream 选项,尾部字节留在内部。
不容易想到的是,进程在 response 结束后立刻退出,未等待收尾。
第一件事是把解码方式改成 stream 模式
node -e "const d=new TextDecoder();console.log(d.decode(Buffer.from([0xe4,0xb8,0xad]),{stream:true}))"
再往下在 done 之后补一次 flush
node -e "const d=new TextDecoder();d.decode();console.log('flushed')"
接着打印每次 read 的字节数确认末尾是否为空
node -e "fetch('http://127.0.0.1:8000/v1/chat/completions',{method:'POST',body:'{}'}).then(r=>r.body.getReader().read()).then(x=>console.log(x.done,x.value&&x.value.length))"
等前面都确认过,再改成异步迭代器写法避免漏读
node -e "for await (const c of [1,2,3]) console.log(c)"
命令输出我都留了记录,免得改完记不清原来的值。
别只看报错消失,验证要多做一步。长回答的末尾字符完整,字节数与 curl 抓到的响应体一致,连续十次请求都没有截断。
补一句。流式读的最后一块经常是空块,但那之前还有未解码的字节,done 之后必须再 flush 一次。拖得越久,排查成本越高,因为现场状态会被后续操作覆盖。
