Node 18环境下node-rest-client执行完成后出现异常TypeError
使用node-rest-client结合nock模拟流响应时的TypeError问题解决
问题概述
使用node-rest-client发起请求,通过nock模拟流响应时,出现以下错误:
TypeError: The "stream" argument must be an instance of ReadableStream, WritableStream, or Stream. Received an instance of Socket
控制台能正常打印响应内容,但后续会抛出未处理的错误事件。
可复现代码
const NodeRestClient = require('node-rest-client').Client; const nock = require('nock'); const { Readable } = require('stream'); // 创建字符串转换的可读流 const streamResponse = new Readable(); streamResponse._read = () => {}; streamResponse.push('Hello, this is a streamable buffer response'); streamResponse.push(null); const apiUrl = 'https://api.example.com'; // 用nock拦截请求并返回流响应 nock(apiUrl) .get('/endpoint') .reply(200, streamResponse, { 'Content-Type': 'application/octet-stream' }); // node-rest-client发起请求 const client = new NodeRestClient(); client.get(`${apiUrl}/endpoint`, { responseType: 'arraybuffer' }, (data, response) => { if (response.statusCode === 200) { console.log('Mocked Streamable Buffer Response:', data.toString('utf-8')); } else { console.error('Error:', response.statusCode, data); } });
错误堆栈信息
Mocked Streamable Buffer Response: Hello, this is a streamable buffer response node:events:495 throw er; // Unhandled 'error' event ^ <ref *2> TypeError [ERR_INVALID_ARG_TYPE]: The "stream" argument must be an instance of ReadableStream, WritableStream, or Stream. Received an instance of Socket at new NodeError (node:internal/errors:405:5) at eos (node:internal/streams/end-of-stream:74:11) at IncomingMessage._destroy (node:_http_incoming:234:21) at _destroy (node:internal/streams/destroy:109:10) at IncomingMessage.destroy (node:internal/streams/destroy:71:5) at endReadableNT (node:internal/streams/readable:1384:16) at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
问题成因
该错误源于nock与node-rest-client的流处理兼容性问题:
- nock在模拟流响应时,会将自定义的Readable流绑定到底层的Socket实例上;
- Node.js内置的
eos(end-of-stream)工具函数在处理流销毁时,误将Socket实例当作普通流传入; - 在部分Node.js版本中,Socket实例的原型链不被
eos的类型校验识别为标准Stream类实例,最终触发类型错误。
解决方法
方法1:直接返回Buffer替代流
既然请求指定了responseType: 'arraybuffer',可以跳过流的创建,直接将响应内容转为Buffer传给nock:
const NodeRestClient = require('node-rest-client').Client; const nock = require('nock'); // 用Buffer替代可读流 const bufferResponse = Buffer.from('Hello, this is a streamable buffer response'); const apiUrl = 'https://api.example.com'; nock(apiUrl) .get('/endpoint') .reply(200, bufferResponse, { 'Content-Type': 'application/octet-stream' }); const client = new NodeRestClient(); client.get(`${apiUrl}/endpoint`, { responseType: 'arraybuffer' }, (data, response) => { if (response.statusCode === 200) { console.log('Mocked Streamable Buffer Response:', data.toString('utf-8')); } else { console.error('Error:', response.statusCode, data); } });
方法2:修改Socket原型链(临时hack)
如果必须使用流模拟,可以通过修改Socket的原型链,让它被识别为Stream实例:
const { Stream } = require('stream'); const net = require('net'); // 让Socket继承Stream原型,通过类型校验 Object.setPrototypeOf(net.Socket.prototype, Stream.prototype); // 后续原代码保持不变 const NodeRestClient = require('node-rest-client').Client; const nock = require('nock'); const { Readable } = require('stream'); const streamResponse = new Readable(); streamResponse._read = () => {}; streamResponse.push('Hello, this is a streamable buffer response'); streamResponse.push(null); const apiUrl = 'https://api.example.com'; nock(apiUrl) .get('/endpoint') .reply(200, streamResponse, { 'Content-Type': 'application/octet-stream' }); const client = new NodeRestClient(); client.get(`${apiUrl}/endpoint`, { responseType: 'arraybuffer' }, (data, response) => { if (response.statusCode === 200) { console.log('Mocked Streamable Buffer Response:', data.toString('utf-8')); } else { console.error('Error:', response.statusCode, data); } });
替代方案:更换HTTP客户端
使用axios替代node-rest-client,它对nock的流模拟兼容性更好:
const axios = require('axios'); const nock = require('nock'); const { Readable } = require('stream'); const streamResponse = new Readable(); streamResponse._read = () => {}; streamResponse.push('Hello, this is a streamable buffer response'); streamResponse.push(null); const apiUrl = 'https://api.example.com'; nock(apiUrl) .get('/endpoint') .reply(200, streamResponse, { 'Content-Type': 'application/octet-stream' }); axios.get(`${apiUrl}/endpoint`, { responseType: 'stream' }) .then(response => { let data = ''; response.data.on('data', chunk => { data += chunk.toString(); }); response.data.on('end', () => { console.log('Mocked Stream Response:', data); }); }) .catch(err => { console.error('Error:', err); });
内容的提问来源于stack exchange,提问作者harish6
相关产品推荐
相关产品推荐

