添加setTimeout后data.map函数二次调用失效问题排查
解决定时API调用时的TypeError: data.map is not a function问题
嘿,我一眼就定位到你这个问题的核心了——全局变量cnt在第一次脚本执行完毕后没有被重置!
咱们来捋一遍:第一次运行callIt时,cnt从0开始递增,直到等于symbols的长度(也就是6),这时候所有符号都处理完了。但当每小时的定时任务再次触发callIt时,cnt还是6,这时候symbols[cnt]就是undefined,你请求的API地址会变成https://api.binance.com/api/v3/klines?symbol=undefined&interval=30m&limit=1。Binance的API遇到这种无效参数,返回的不是你预期的K线数组,而是一个错误对象,自然就没法调用.map()方法了,这就是你看到那个报错的原因。
修复方案
我给你调整了代码,不仅解决了cnt重置的问题,还优化了几个细节让脚本更健壮:
const { MongoClient } = require('mongodb'); const schedule = require('node-schedule'); const fetch = require("node-fetch"); const symbols = ["ZRXBTC", "LENDBTC", "AEBTC", "AIONBTC", "ALGOBTC", "ARDRBTC"]; // 复用MongoDB连接,避免每次插入都重新建立连接 let mongoClient; // 初始化MongoDB连接(只执行一次) async function initMongoConnection() { const url = 'mongodb+srv://username:password3@cluster0-1kunr.mongodb.net/<dbname>?retryWrites=true&w=majority'; try { mongoClient = await MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }); console.log('MongoDB连接成功,准备启动定时任务'); } catch (err) { console.error('MongoDB初始化失败:', err); process.exit(1); // 连接失败直接退出脚本 } } const processAllSymbols = async () => { // 每次定时任务触发时,重置cnt为0,确保从第一个符号开始处理 let cnt = 0; const processSingleSymbol = async () => { if (cnt >= symbols.length) { console.log('所有符号数据处理完成'); return; } const currentSymbol = symbols[cnt]; try { const res = await fetch(`https://api.binance.com/api/v3/klines?symbol=${currentSymbol}&interval=30m&limit=1`); const data = await res.json(); // 先检查API返回是否为数组,避免无效数据导致崩溃 if (!Array.isArray(data)) { console.error(`获取${currentSymbol}数据失败,API返回:`, data); cnt++; setTimeout(processSingleSymbol, 3000); return; } // 转换数据格式 const formattedData = data.map(d => ({ Open: parseFloat(d[1]), High: parseFloat(d[2]), Low: parseFloat(d[3]), Close: parseFloat(d[4]), Volume: parseFloat(d[5]), Timespan: 30, })); console.log(`处理完成${currentSymbol}:`, formattedData); await saveToDatabase(formattedData, currentSymbol); cnt++; // 延迟3秒处理下一个符号,避免触发API限流 setTimeout(processSingleSymbol, 3000); } catch (err) { console.error(`处理${currentSymbol}时出错:`, err); cnt++; setTimeout(processSingleSymbol, 3000); } }; await processSingleSymbol(); }; const saveToDatabase = async (formattedData, symbolName) => { const today = new Date(); // 格式化日期时间,确保格式统一(比如1月→01,9点→09) const date = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; const time = `${String(today.getHours()).padStart(2, '0')}:${String(today.getMinutes()).padStart(2, '0')}:${String(today.getSeconds()).padStart(2, '0')}`; const dateTime = `${date} ${time}`; try { const db = mongoClient.db('CryptoCurrencies'); const collection = db.collection(symbolName); const insertResult = await collection.insertOne({ Name: symbolName, Array: formattedData, Date: dateTime }); console.log(`成功插入1条数据到${symbolName}集合,ID: ${insertResult.insertedId}`); } catch (error) { console.error(`存入${symbolName}数据失败:`, error); } }; // 先初始化MongoDB连接,再启动每小时的定时任务 initMongoConnection().then(() => { // 每小时整点执行一次(原代码的*/0 * * * *是每分钟执行,应该是0 * * * *才是每小时) const job = schedule.scheduleJob('0 * * * *', processAllSymbols); console.log(`定时任务已启动,将在每小时整点执行`); });
关键优化点说明
- 重置cnt变量:把
cnt放到processAllSymbols函数内部,每次定时任务触发都会重置为0,确保每次都从第一个符号开始处理。 - 复用MongoDB连接:原来的代码每次插入都创建新连接,这会造成不必要的性能损耗,现在改为初始化时建立一次连接,后续复用。
- 添加API返回校验:增加了
Array.isArray(data)的判断,即使API返回错误信息,脚本也能继续处理下一个符号,不会直接崩溃。 - 使用async/await简化异步逻辑:相比原来的Promise链式调用,代码可读性更高,调试也更方便。
- 修正定时任务规则:原代码的
*/0 * * * *是每分钟执行一次,你需要的是每小时执行,所以改成0 * * * *(整点触发)。 - 格式化日期时间:用
padStart确保日期和时间的格式统一,避免出现类似"2024-2-3 8:5:3"这种不规范的格式。
内容的提问来源于stack exchange,提问作者Hexycode
相关产品推荐
相关产品推荐

