记一次 node-fetch 使用时踩的坑

记一次 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 了多个目标,一定要注意他们相互之间的影响。
  • 对于一项技术,唯有在透彻理解其机制后,才能更好的运用。
posted @ 2017-03-09 11:35  snadn  阅读(9558)  评论(1编辑  收藏  举报