Node.js的unref和ref方法是异步资源管理的重要接口,主要作用于定时器、子进程、监听器等实现了unref和ref方法的对象,核心作用是调整这些异步资源对事件循环退出时机的影响。

unref和ref方法的基础概念
unref方法的作用是:当一个异步资源调用unref之后,如果该资源是当前事件循环中唯一活跃的异步资源,事件循环不会因为这个资源而继续保持运行,会在完成其他任务后直接退出。
ref方法则是unref的逆操作:当异步资源调用ref之后,它会重新被纳入事件循环的活跃资源计数中,只要该资源还存在未完成的任务,事件循环就会等待它完成。
需要注意的是,不是所有Node.js的异步对象都支持这两个方法,常见的支持对象包括setTimeout、setInterval返回的定时器对象,以及child_process模块创建的子进程对象等。
unref和ref对事件循环的影响机制
Node.js的事件循环会维护一个活跃异步资源的计数,当有新的异步任务注册时计数加1,任务完成时计数减1。当计数降为0时,事件循环就会退出,程序随之结束。
unref的作用逻辑
调用unref之后,该异步资源会从活跃资源计数中移除,相当于告诉事件循环:这个资源的任务不是必须等待的。此时如果其他所有活跃资源都已完成,哪怕这个unref的资源还没触发回调,事件循环也会直接退出。
以下是一个简单的示例,展示unref对程序退出的影响:
// 创建一个1秒后触发的定时器
const timer = setTimeout(() => {
console.log('定时器回调执行');
}, 1000);
// 调用unref方法
timer.unref();
console.log('程序继续执行,等待事件循环退出');
运行上述代码,会发现程序不会等待1秒后输出定时器回调内容,而是直接打印"程序继续执行,等待事件循环退出"后就退出了,因为定时器已经被unref,不再是活跃资源,事件循环没有其他任务就直接退出了。
ref的作用逻辑
调用ref之后,异步资源会重新回到活跃资源计数中,事件循环会等待它的任务完成。如果之前调用过unref,再调用ref就能恢复该资源对事件循环的影响。
修改上面的示例,在unref之后调用ref:
const timer = setTimeout(() => {
console.log('定时器回调执行');
}, 1000);
timer.unref();
// 重新调用ref,恢复定时器对事件循环的影响
timer.ref();
console.log('程序继续执行,等待定时器回调');
此时运行代码,程序会等待1秒后输出"定时器回调执行",再退出,因为定时器重新成为了活跃资源,事件循环需要等待它的任务完成。
不同场景下的实际表现
定时器场景
除了setTimeout,setInterval的返回对象同样支持unref和ref。如果对一个循环的定时器调用unref,那么即使定时器一直在循环执行,只要没有其他活跃资源,事件循环也会退出,定时器随之停止。
const intervalTimer = setInterval(() => {
console.log('间隔定时器执行');
}, 500);
intervalTimer.unref();
console.log('程序不会等待间隔定时器,直接退出');
上述代码中,间隔定时器调用了unref,程序不会持续输出"间隔定时器执行",而是直接退出。
子进程场景
使用child_process创建的子进程对象也支持unref和ref。如果子进程调用了unref,那么主进程的事件循环不会等待子进程结束,子进程可能会在后台继续运行,也可能随主进程退出而终止,具体取决于系统调度。
const { spawn } = require('child_process');
const child = spawn('sleep', ['2']);
child.unref();
console.log('主进程不会等待子进程结束,直接退出');
使用注意事项
- 只有支持unref和ref方法的对象才能调用这两个方法,普通Promise、回调函数的对象没有这两个方法,调用会报错。
- unref之后如果异步资源已经触发了回调,再调用ref是没有意义的,因为资源已经完成,不会回到活跃计数中。
- 不要滥用unref,除非你明确知道该异步资源的任务不影响程序核心逻辑,否则可能导致预期之外的程序提前退出。
适用场景总结
unref适合用在可选的后台任务场景,比如非核心的统计上报、日志异步写入等,这些任务即使没有完成也不影响主程序的核心逻辑,调用unref可以避免这些任务阻塞程序退出。
ref则适合用在需要恢复异步资源必要性的场景,比如之前临时unref了一个定时器,后续业务逻辑需要等待这个定时器的回调,就可以调用ref恢复它的活跃状态。
合理搭配unref和ref方法,可以更灵活地控制Node.js程序的事件循环生命周期,优化程序的运行效率。