Closing TCP/UDP sockets with timeouts and error handling in Nodejs

I'm not a big fan of the built in dgram and net libraries in Nodejs, but it's really easy to create sockets and write networking applications with Nodejs. One of the biggest issues I'm constantly seeing is the lack of cleanup functionalities when using the socket libraries in Nodejs.
This code is from our DNS npm package that we use in VioletnorthYou can find the Violetnorth - DNS npm package here:github.com/violetnorth/dns
So I'm going to talk about a quick way to cleanup sockets with timeouts and overall error handling when dealing with sockets. If you don't implement some sort of timeout, you are going to run out of available sockets especially when using TCP sockets. Sockets usually hang and there needs to be timeout handling to close the socket if it's hanging, which is not super apparent with Nodejs.
Here is an example function that uses a TCP socket to do a DNS lookup.
TCP socket with timeout
const dns = require("dns");
const net = require("net");
const dnsPacket = require("dns-packet");

const resolveTCP = (packet, addr, port = 53, timeout) => {
  return new Promise((resolve, reject) => {
    const socket = new net.Socket();

    const id = setTimeout(() => {
      clearTimeout(id);
      socket.destroy();
      reject("timed out");
      return;
    }, parseInt(timeout));

    socket.connect(parseInt(port), addr, () => {
      socket.write(packet);
    });

    let message = Buffer.alloc(4096);
    socket.on("data", data => {
      message = Buffer.concat([message, data], message.length + data.length);
    });

    socket.on("drain", () => {
      clearTimeout(id); // Clear the timeout if you are going to close the socket manually.
      socket.destroy();
      resolve(dnsPacket.decode(message));
      return;
    });

    socket.on("end", () => {
      clearTimeout(id); // Clear the timeout if you are going to close the socket manually.
      socket.destroy();
      resolve(dnsPacket.decode(message));
      return;
    });

    socket.on("error", err => {
      reject(err);
      return;
    });

    socket.on("close", function() {
      resolve(dnsPacket.decode(message));
      return;
    });
  });
};
Another example function that uses a UDP socket to do a DNS lookup.
UDP socket with timeout
const dns = require("dns");
const dgram = require("dgram");
const dnsPacket = require("dns-packet");

const _resolveUDP = (packet, addr, port = 53, timeout) => {
  return new Promise((resolve, reject) => {
    const socket = dgram.createSocket("udp4");

    const id = setTimeout(() => {
      clearTimeout(id);
      socket.close();
      reject("timed out");
      return;
    }, parseInt(timeout));

    socket.on("message", message => {
      clearTimeout(id); // Clear the timeout if you are going to close the socket manually.
      socket.close();
      resolve(dnsPacket.decode(message));
      return;
    });

    socket.on("error", err => {
      clearTimeout(id); // Clear the timeout if you are going to close the socket manually.
      socket.close();
      reject(err);
      return;
    });

    socket.send(packet, 0, packet.length, parseInt(port), addr, err => {
      if (err) {
        clearTimeout(id); // Clear the timeout if you are going to close the socket manually.
        socket.close();
        reject(err);
      }
    });
  });
};
The idea is basically to create a named timeout function which will close the socket after the specified timeout milliseconds, but you have to clear the timeout function every time you close the socket manually (in the case of an error for example). Otherwise, you will see unhandled errors due to trying to close an already closed socket.
Anyways, that's pretty much it, error handling and timeouts with Nodejs sockets.
Koray Gocmen
Koray Gocmen

University of Toronto, Computer Engineering.

Architected and implemented reliable infrastructures and worked as the lead developer for multiple startups.