Skip to main content

On mobile? Send a link to your computer to download HTTP Toolkit there:

No spam, no newsletters - just a quick & easy download link

On mobile? Send a link to your computer to download HTTP Toolkit there:

No spam, no newsletters - just a quick & easy download link

node.js

errors

Unblocking Node With Unref()

Node.js runs on an event loopopens in a new tab. It holds a queue of tasks to run, and runs them, one by one. New tasks appear on the queue while it runs, added by your code (setTimeout) or outside events (a network connection), and the process simply continues until the queue is empty.

That's all great, until it isn't.

Occasionally you want to break out of that model. What happens if you want to run a schedule task on a fixed interval indefinitely? Typically, life gets difficult: you need to include & manage an explicit shutdown process for that interval, and if you ever forget to shut it down then the process will keep running forever, with no explanation. Ouch.

I hit ran into this whilst working on Mockttpopens in a new tab (the HTTP interception & testing library behind HTTP Toolkit). Mockttp needs to keep track of your current local IP addresses, to help detect and warn about request loops. That data can change occasionally, so it needs to poll it on an interval, but it's very annoying to have to remember to carefully shut that process down in addition to everything else.

Fortunately, it turns out you can fix this easily! Enter unref:

Timeout.Unref()

Timer functions like setInterval and setTimeout in Node.js return a Timeout objectopens in a new tab, representing the ongoing timer.

These can be passed to clearInterval or clearTimeout to shutdown the timer entirely, but they also have a little-used unref() method. This does something magical: it keeps running your code, but stops it from keeping the process alive. Like so:

Code example

Code example// Update my data every 10 seconds
const interval = setInterval(() => updateMyData(), 10000);
// But don't let that keep the process alive!
interval.unref();

// Log a message if the app is still running 10 seconds from now
const timeout = setTimeout(() => console.log('Still going'), 10000);
// But still shutdown cleanly if it wants to stop before then:
timeout.unref();

This functions like a flag you can set on your timers, marking them as tasks that node doesn't need to wait for. They'll run as normal while the process is alive, but if the rest of the event queue is empty then they're ignored, and the process exits anyway.

You can also mark the timer as important again with timer.ref() or (in Node 11+ only) check whether it's currently configured to block exit of the process with timer.hasRef().

If you want to see this in action, you can check out the fix for Mockttp over here: https://github.com/httptoolkit/mockttp/blob/master/src/util/socket-util.ts#L58-L71

Gotchas

There's three last things worth noting here:

  • Although this can let you skip complicated cleanup processes, it doesn't make them worthless. Especially if your timer is doing something expensive, it's very often useful to provide an explicit shutdown command instead. This isn't a subsitute for cleaning up after yourself!
  • This can come with a small performance cost, as it's actually implemented using a separate scheduled task. Using a few is fine, but if you're creating very large numbers of these you might see a performance impact.
  • You shouldn't be using this everywhere. If you use this on a timeout you care about, you'll discover that your app is unexpectedly exiting half way through, way before you're expecting. This is similar to weak maps: it's a tool for specific situations, not an option for every day.

While you're here, if you like Node & want to supercharge your debugging skills, take a look at HTTP Toolkitopens in a new tab. One-click HTTP(S) interception & debugging for any Node.js script, tool or server (and lots of other tools too).

Share this post:

Blog newsletter

Become an HTTP & debugging expert, by subscribing to receive new posts like these emailed straight to your inbox:

Related content

apis

Designing API Errors

When everything goes smoothly with an API, life is pretty straightforward: you request a resource, and voilà, you get it. You trigger a procedure, and the API politely informs you it’s all gone to plan. But what happens when something goes pear-shaped? Well, that’s where things can get a bit tricky. HTTP status codes are like a first aid kit: they’re handy, but they won’t fix everything. They give you a broad idea of what’s gone wrong, which can help plenty of tools and developers make reasonable assumptions, like:

debugging

6 ways to debug an exploding Docker container

Everything crashes. Sometimes things crash when they're running inside a Docker container though, and then all of a sudden it can get much more difficult to work out why, or what the hell to do next.

node.js

Automatic npm publishing, with GitHub Actions & npm granular tokens

This week, at long last, GitHub announced granular access tokens for npm. This is a big deal! It's great for security generally, but also particularly useful if you maintain any npm packages, as it removes the main downside of automating npm publishing, by allowing you to give CI jobs only a very limited token instead of full 2FA-free access to your account. In the past, I've wished for this, because I maintain a fair few npm packages including some very widely used ones. The previous solution of "just disable 2FA on your account, create an all-powerful access token with global access to every package, and give that token to your CI job" was not a comfortable one.