记一次 node-fetch 使用时踩的坑
背景
在使用如下代码发起请求的时候,个别接口出现了无法得到结果的情况。
async function req() {
const res = fetch(xxx);
let resData = null;
try {
resData = await res.clone().json();
} catch (err) {}
if (!resData) {
log(await res.clone().text());
}
}
追查
首先
我通过其他请求工具,发现出问题的接口是正常响应的。也就是确认了问题是出在自己的代码里面的。
然后
我在 try 后面打断点,想看一下 resData 收到的是什么,发现程序根本走不到那,但是 try 里面也没有报错。
到这一步,我就觉得问题有点奇怪了。
接下来
我只能到 node-fetch 的代码里面去加断点看一下是什么情况了。
在这过程中又出现了很诡异的一幕。我分别在 on('data')
和 on('end')
的时候加调试信息,发现 end
事件没有触发,但如果在 on('data')
中添加断点的话,end
能够触发,而整个请求也能收到响应结果了。
通过进一步调试,我发现如果不对 node stream 的模型做一个系统的了解,我可能会很难查出问题的原因。但对于问题的解决,依稀记得之前使用 res.json()
的时候是没有问题的。
尝试解决
于是,我尝试着将 res.clone().json()
改成 res.json()
,果然问题不在出现,请求顺利接收。这时候我开始怀疑是不是 node-fetch 在 clone 的实现上有 bug 。但看了看源码,思路很清晰,感觉不出哪有问题啊。所以,没有了解清楚 stream 相关的思路前,还不能妄下定论。
而对于 .json
失败后,需要记录响应文本的情况,就改用 res._convert().toString()
实现了。
原因探究
后来,我又通过一步一步断点调试和对 stream 的文档和源码的查看,终于定位了问题。
原来,node-fetch 在 clone 的时候产生了两个目标,源码如下:
p1 = new PassThrough();
p2 = new PassThrough();
body.pipe(p1);
body.pipe(p2);
// set instance body to teed body and return the other teed body
instance.body = p1;
body = p2;
然后我的代码使用了其中一个即 res.clone
的返回进行 .json
操作,相当于 p2.json()
。但对另一个 res 即 p1 没有做处理。
而 stream 有一个 back pressure 机制,因为 p1 没有消耗,缓存数据满时会使其源 pause,从而导致 p2 也不能结束。
结语
- 使用 stream 时,若 pipe 了多个目标,一定要注意他们相互之间的影响。
- 对于一项技术,唯有在透彻理解其机制后,才能更好的运用。