記一次 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 了多個目標,一定要注意他們相互之間的影響。
- 對於一項技術,唯有在透徹理解其機制后,才能更好的運用。