Node.js第17课:定时器,与浏览器、ref和ref的区别
大家好,本课将讨论我们在Nodejs中可以使用的不同计时器。我们还将讨论什么是ref以及如何使用它们。让我们开始吧。
什么是Nodejs中的定时器?
Nodejs中的定时器功能与我们在浏览器中得到的类似,但也有细微差别。浏览器为我们提供了一个窗口对象,它为我们提供了定时器功能。Nodejs将所有的功能捆绑在Nodejs本身,并模仿浏览器的行为。由于Nodejs捆绑了这些功能,你不必要求任何东西就可以在项目中使用它。
我们将主要讨论三种类型的定时器:
- setTimeout
- set setInterval
- SetImmediate
1.设置超时
设置超时允许我们在一定时间后运行一段代码。我们在第一个参数中把这段 "代码 "作为一个回调函数传递。而第二个参数接收一个数字,这个数字表示毫秒,之后回调会被调用:
setTimeout(function () {
console.log('5 seconds have passed');
}, 5000);
如果我们看到上面的代码,回调函数在5秒后记录了一条语句,因为我们传递了5000作为第二个参数。虽然这看起来与网络上的情况非常相似,但事实并非如此。
Nodejs使用Event Loop来排队异步回调,只有在当前执行完成且调用栈为空时才会调用待定回调。你可以阅读之前的文章,其中详细介绍了Event Loop和Call Stack。由于Nodejs的这种非阻塞异步行为,我们不能保证回调会在5秒后准确运行。
是的,大约是5秒。但是,实际时间将取决于执行期间队列中存在的回调。任何回调的出现都会将setTimeout的调用往后推,这就会延迟执行时间。让我们看一下这个例子:
console.log('Before timeout');
setTimeout(function () {
console.log('Set timeout over');
}, 0);
console.log('After timeout');
你认为上面的代码会记录什么?即使等待时间为0秒,Set timeout over log将最后出现。原因和我们在上面一段中谈到的是一样的。任何异步回调都是由Event Loop处理的,它在当前堆栈为空时运行回调,也就是说,其他两个日志被打印出来。这就是为什么在Nodejs中不能保证超时时间的准确性。
2.设置间隔
我希望你能理解我们上面刚刚讨论的设置超时。设置时间间隔与此相当类似,但它不是在超时后运行一次函数,而是一次又一次地运行,直到你停止它。让我们看一个例子:
setInterval(function () {
console.log('1 second has passed');
}, 1000);
上面的代码会在整个进程的生命周期中每秒钟打印一次日志,除非我们自己停止它。现在,什么时候使用它呢?
假设你正在构建一个倒计时,你必须在每一秒钟都显示计数;在这种情况下,setInterval将非常方便。
我们稍后将学习如何停止它,但首先,让我们看看第三个定时器函数。
3.设置立即执行
还记得我们是如何区分Node的计时器函数和浏览器中的计时器函数的吗?每一个传递给setTimeout的回调都会在执行前进入事件循环和调用堆栈。在这种情况下,Set Immediate可以帮助我们。它与setTimeout的0ms超时非常相似,但不太一样。让我们来看看如何:
console.log('Before timeout');
setTimeout(function () {
console.log('Set timeout over');
}, 0);
setImmediate(function () {
console.log('Run Immediate call'); // look this closely
});
console.log('After set immediate');
我将分享日志来解释发生了什么:
Before timeout
After set immediate
Set timeout over
Run Immediate call
任何作为setImmediate()参数传递的函数都是在事件循环的下一次迭代中执行的回调。当我们执行上述代码时,setTimeout将进入循环,然后是setImmediate()。因为我们说setImmediate会在下一个迭代中运行,所以它在超时后被执行。让我们再看一个例子:
console.log('Before timeout');
setTimeout(function () {
console.log('Set timeout over');
}, 0);
setImmediate(function () {
console.log('Run Immediate call'); // look this closely
});
setTimeout(function () {
console.log('Another timeout over');
}, 0);
console.log('After set immediate');
// logs
// Before timeout
// After set immediate
// Set timeout over
// Another timeout over
// Run Immediate call
尽管setImmediate是在第二个位置进入循环,但它是最后被执行的。这就是我们所说的 "在事件循环的下一次迭代中执行的回调 "的意思。
那么,什么时候使用它呢?只要你想在回调中的所有内容执行完毕后排队回调,就可以使用 setImmediate() 。把它看作是说:"当你完成所有的I/O或异步工作时,运行这个回调"。
现在我们了解了所有三种类型的定时器以及如何启动它们。让我们来看看如何停止它们。
如何停止定时器功能
我们谈到的所有的定时器都是在调度一些未来要执行的动作。所以我们也应该学会在我们想要的时候取消这个未来的执行。为此,我们将不得不理解'ref'。
每当你创建一个定时器时,它就会返回一个对定时器的引用,我们可以用它来更新其行为。把这个引用看作是一个唯一的ID,它可以让我们掌握定时器的情况。让我们看看下面的代码:
const interval = setInterval(() => {
console.log('tik');
}, 1000);
setTimeout(() => {
clearInterval(interval);
}, 4000);
// logs
// tik
// tik
// tik
上面的代码创建了一个间隔,每秒钟打印'tik'。我们将这个引用存储到一个变量'ref'中。
然后在4秒后将该ref传递给一个函数clearInterval()。由于该间隔在第4秒时被取消,我们只看到3条'tik'的日志。
同样地,我们得到了分别清除这3个计时器的函数。它们是:
- clearTimeout()
- clearInterval()
- clearImmediate()
这三种情况下的用法都很相似。你在一个变量中存储一个引用,然后将其传递给clear函数以取消执行。
默认情况下,只要定时器处于活动状态,Nodejs就会保持事件循环运行。这让定时器在未来被执行,并保持进程退出。 interval.ref() 和 interval.unref()函数可以控制这种默认行为。
timeout.ref()或interval.ref()将保持事件循环的活动,只要定时器处于活动状态。它在默认情况下总是被调用,所以不需要再次调用。
timeout.unref()或interval.unref()将阻止计时器在没有事件循环活动的情况下处于活动状态。这将防止副作用的发生。在定时器活动时退出进程不会有副作用。