使用NodeJS+Tedious向SQL Server插入数据报ERR_INVALID_ARG_TYPE错误如何解决
错误直接原因
你代码中事件监听的写法不符合Node事件模块的API规范,是触发本次报错的直接根源:
Request.on('requestCompleted',"row", function (columns) {
EventEmitter.on()方法仅接受两个参数:第一个为事件名称,第二个为对应的回调函数。你多传入了字符串"row"作为第二个参数,事件模块会将它识别为监听函数处理,自然就抛出「listener必须为函数类型,收到了字符串row」的类型错误。
其他必须修复的逻辑问题
即使修复了上述报错,你的代码还存在多个逻辑错误,会导致插入功能无法正常运行:
- 覆盖了Request构造函数变量
你最开始引入了tedious的Request构造函数:
var Request = require("tedious").Request;
但在循环中直接把这个构造函数变量赋值为了请求实例:
Request = new Request(...)
第一次循环执行后,Request变量就变成了实例对象,第二次循环时无法再调用构造函数生成新的请求,需要修改实例变量名,比如改为const request = new Request(...)。
异步逻辑未等待,插入时数据数组为空
calcWeather是async异步函数,内部调用的天气接口请求也是异步逻辑,你直接调用calcWeather()后立刻遍历lstValid数组时,所有接口请求还没有返回结果,数组是空的。就算你给calcWeather()加了await,内部循环的fetch请求也没有用Promise.all收集等待,还是拿不到完整数据。循环条件数组越界
你写的循环条件for (var m = 0; m <= lstValid.length; m++)有问题,数组下标范围是0到length-1,等于length的时候会取到undefined,需要改为m < lstValid.length。单个请求完成就关闭数据库连接
你在每个请求的requestCompleted事件中都调用了connection.close(),第一个插入请求执行完成就会把数据库连接关掉,后续所有请求都会执行失败,需要等所有插入请求全部完成后再关闭连接。插入参数取值错误
你写的Request.addParameter("IdOficina", TYPES.SmallInt, lstValid[m])中,lstValid[m]是完整的对象,需要取对应属性传入,比如lstValid[m].Id_Oficina。
修复后参考代码
事件监听部分修改
row和requestCompleted是两个独立的事件,需要分开监听:
// 监听行返回事件 request.on('row', function (columns) { columns.forEach(function (column) { if (column.value === null) { console.log("NULL"); } else { console.log("Product id of inserted item is " + column.value); } }); }); // 监听请求完成事件 request.on("requestCompleted", function (rowCount, more) { // 此处不要直接关闭连接,等所有请求完成再关 });
异步逻辑修改
改造calcWeather等待所有天气接口请求完成:
async function calcWeather() { const info = await fetch("../json/data.json").then(res => res.json()); // 收集所有天气接口的请求Promise const fetchPromises = []; for (var i in info) { const _idOficina = info[i][0].IdOficina; const lat = info[i][0].latjson; const long = info[i][0].lonjson; const base = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${long}&appid=${api_key}&units=metric&lang=sp`; fetchPromises.push( fetch(base) .then(res => res.json()) .then(data => { var myObject = { Id_Oficina: _idOficina, // 补充其他属性赋值 }; if (myObject.Temperatura < 99) { lstValid.push(myObject); } }) ) } // 等待所有天气请求执行完成 await Promise.all(fetchPromises); }
插入逻辑修改
改造executeStatement1等待数据准备完成,批量执行插入:
async function executeStatement1() { // 等待所有插入数据准备完成 await calcWeather(); // 收集所有插入请求的Promise const insertPromises = []; for (var m = 0; m < lstValid.length; m++) { insertPromises.push(new Promise((resolve, reject) => { const request = new Request( "INSERT INTO TB_BI_CSL_RegistroTemperaturaXidOdicina (IdOficina, Humedad, Nubes, Sensacion, Temperatura, Descripcion) VALUES (@IdOficina, @Humedad, @Nubes, @Sensacion, @Temperatura, @Descripcion)", function (err) { if (err) { console.log("Couldn't insert data: " + err); reject(err); } else { resolve(); } } ); // 取对象对应属性传入参数 request.addParameter("IdOficina", TYPES.SmallInt, lstValid[m].Id_Oficina); request.addParameter("Humedad", TYPES.Int, lstValid[m].Humedad); // 补全其他参数的赋值 request.on('row', function (columns) { columns.forEach(function (column) { if (column.value === null) { console.log("NULL"); } else { console.log("Product id of inserted item is " + column.value); } }); }); connection.execSql(request); })) } // 所有插入完成后关闭连接 await Promise.all(insertPromises); connection.close(); }
内容的提问来源于stack exchange,提问作者Martín JF

