current commit
260728.6 · net: add experimental net/promises API
'use strict';

const { once } = require('events');
const {
  validateAbortSignal,
  validateObject,
} = require('internal/validators');
const { kEmptyObject } = require('internal/util');

// Lazily loaded to avoid a require cycle with the `net` module, which exposes
// this namespace through its `promises` getter.
let net;
function lazyNet() {
  net ??= require('net');
  return net;
}

// Resolves with a connected `net.Socket` once the `'connect'` event fires, and
// rejects if the connection fails or the optional `signal` is aborted.
async function connect(...args) {
  const lazy = lazyNet();
  const options = lazy._normalizeArgs(args)[0];
  const { signal } = options;
  if (signal !== undefined) {
    validateAbortSignal(signal, 'options.signal');
    signal.throwIfAborted();
  }

  // Strip the signal so the socket does not also install its own abort
  // handling; rejecting and destroying below fully tears the socket down.
  const socket = lazy.connect({ ...options, signal: undefined });

  try {
    await once(socket, 'connect', signal !== undefined ? { signal } : kEmptyObject);
  } catch (err) {
    socket.destroy();
    throw err;
  }
  return socket;
}

// Creates a server and resolves with it once it is listening, rejecting if it
// fails to bind or the optional `signal` is aborted.
async function listen(options = kEmptyObject) {
  validateObject(options, 'options');
  const { signal, connectionListener } = options;
  if (signal !== undefined) {
    validateAbortSignal(signal, 'options.signal');
    signal.throwIfAborted();
  }

  const lazy = lazyNet();
  const server = lazy.createServer(options, connectionListener);

  try {
    // Default to an ephemeral port when no listen target is supplied, matching
    // `server.listen()` with no arguments; passing a target-less options object
    // (the `{}` default, or e.g. `{ signal }`) straight to listen() would throw
    // ERR_INVALID_ARG_VALUE. `signal` is passed through so net installs its own
    // close-on-abort handler: the signal aborts the server for its entire
    // lifetime, not just the pending listen.
    const hasListenTarget = options.port !== undefined ||
                            options.path !== undefined ||
                            options.fd !== undefined ||
                            options.handle !== undefined;
    server.listen(hasListenTarget ? options : { ...options, port: 0 });
    await once(server, 'listening', signal !== undefined ? { signal } : kEmptyObject);
  } catch (err) {
    // On abort, net's signal handler already closes the server, so closing
    // again would be redundant; on other failures (e.g. a bind error) there
    // is no such handler, so close it here.
    if (!signal?.aborted) {
      server.close();
    }
    throw err;
  }
  return server;
}

module.exports = {
  connect,
  listen,
  get isIP() { return lazyNet().isIP; },
  get isIPv4() { return lazyNet().isIPv4; },
  get isIPv6() { return lazyNet().isIPv6; },
  get BlockList() { return lazyNet().BlockList; },
  get SocketAddress() { return lazyNet().SocketAddress; },
};