问题描述
使用setInterval
运行较长一段时间后出现堆溢出的情况。
代码类似于
setInterval(sendHeartbeat, 30000);function sendHeartbeat() {axios.get(url).then(res => {console.log("success")}).catch(err => {console.error(err.message);})
}
在一些老机器上运行较长时间就会出现堆溢出,在性能好一点的机器则不会。
猜测出现堆溢出的原因
1、sendHeartbeat
方法中有递归调用。
2、sendHeartbeat
函数执行时间较长,可能会导致大量的函数调用堆积在内存中,最终导致堆栈溢出。
很显然sendHeartbeat
方法是没有递归的,那么只有可能是2的原因了。
解决方案
1、增加定时器的间隔
可以增加定时器的间隔,让方法里面的代码有足够的时间去执行。缺点是不能确定增加间隔之后会不会出现堆溢出。
2、使用setTimeout
- 推荐
把上面的代码改造成下面的样子,可以看到我使用了两个setTimeout
做成递归的形式替换了setInterval
,这样sendHeartbeat
方法就会一个个的执行,不会产生堆积。
// 最初的调度
setTimeout(sendHeartbeat, 30000);function sendHeartbeat() {axios.get(url).then(res => {console.log("success")}).catch(err => {console.error(err.message);}).finally(() => {// 调度下一次执行setTimeout(sendHeartbeat, 30000);})
}
3、使用定时任务
但是使用定时任务是不能避免任务堆积的问题的。
const cron = require('node-cron');// 定义定时任务,每30秒执行一次
cron.schedule('*/30 * * * * *', function() {sendHeartbeat();
});function sendHeartbeat() {axios.get(url).then(res => {console.log("success")}).catch(err => {console.error(err.message);})
}
改进版-状态标记
let running = false; // 用于标记任务是否正在运行// 定义定时任务,每30秒执行一次
cron.schedule('*/30 * * * * *', function() {if (!running) { // 如果任务不在运行中,执行任务running = true; // 标记任务为运行中sendHeartbeat().then(() => {running = false; // 标记任务为未运行}).catch(err => {console.error(err.message);running = false; // 标记任务为未运行});}
});async function sendHeartbeat() {await axios.get(url).then(res => {console.log("success")}).catch(err => {console.error(err.message);})
}
总结:
使用setTimeout
的优势在于它会等待函数执行完成后再次调用,而不会像setInterval
那样按照固定时间间隔调用函数。这使得您可以更好地控制函数之间的间隔,避免函数重叠执行,从而减少了堆积和内存问题的风险。
当函数执行时间不确定或可能较长时,使用setTimeout
可以更灵活地调度下一次执行,而不会导致内存问题。此外,使用setTimeout
还可以避免setInterval
可能出现的时间累积偏差问题。
然而,选择解决方案通常取决于具体的应用场景和需求。如果您需要更复杂的定时任务调度和精确的执行时间,那么使用定时任务(比如node-cron
)也是一个不错的选择。