Skip to content

net: add experimental net/promises API#63965

Open
Ethan-Arrowood wants to merge 1 commit into
nodejs:mainfrom
Ethan-Arrowood:net-promise
Open

net: add experimental net/promises API#63965
Ethan-Arrowood wants to merge 1 commit into
nodejs:mainfrom
Ethan-Arrowood:net-promise

Conversation

@Ethan-Arrowood

Copy link
Copy Markdown
Contributor

Adds an experimental net/promises namespace that provides promise-based variants of net's connection and listening operations, mirroring the existing fs/promises and dns/promises modules. It is accessible via require('node:net/promises') or require('node:net').promises.

Motivation

net has no promise-based API. To await a connected socket or a listening server today you have to hand-wire the 'connect'/'listening' and 'error' events into a new Promise, or reach for events.once. There is long-standing demand for a first-class promise API for these one-shot lifecycle moments (Refs: #21482).

net is stream- and event-oriented, so only its one-shot async moments map cleanly to promises (establishing a connection and binding a server). The rest of the api remains the same.

What's added

  • netPromises.connect(options) / connect(path) / connect(port[, host]) resolves with a connected net.Socket once its 'connect' event fires; rejects on connection failure or abort, destroying the socket.
  • netPromises.listen([options]) creates a server, begins listening, and resolves with the listening net.Server once its 'listening' event fires; rejects if it fails to bind or is aborted, closing the server. Accepts a connectionListener option and the usual net.createServer / server.listen options.
  • The synchronous helpers (isIP, isIPv4, isIPv6, BlockList, SocketAddress) are re-exported for convenience.

Both functions accept an optional AbortSignal and reject with an AbortError when it is aborted, consistent with timers/promises and readline/promises.

Example:

import { connect, listen } from 'node:net/promises';

const server = await listen({ port: 0 });
const socket = await connect({ port: server.address().port });

A note on naming (connect/listen, not createConnection/createServer)

The naming here is intentional. This API promisifies actions that complete: connect() resolves once the connection is established, and listen() resolves once the server is listening. The function name is the action being awaited, and the two form a parallel pair.

That is deliberately not the factory taxonomy of the callback API. There, createConnection() is the canonical name because it both creates a socket and initiates connecting. Creation and the action are fused, so the factory name also implies the action. createServer(), by contrast, is a pure factory: it does not listen; listening is the separate server.listen() step. So a createConnection/createServer pairing would be inconsistent in a promise API. createServer has no completion to await. Naming the functions for their actions (connect/listen) keeps the pair coherent, and there is no factory counterpart to alias, so createConnection is intentionally omitted.

Notes

  • New experimental API (Stability: 1). semver-minor.
  • Implemented on top of events.once(emitter, name, { signal }), so error and abort handling match the rest of core.
  • Documentation and tests included.

Refs: #21482
Assisted-by: Claude Opus 4.8 noreply@anthropic.com

Add an experimental `net/promises` namespace, accessible via
`require('node:net/promises')` or `net.promises`, mirroring the existing
`fs/promises` and `dns/promises` modules.

It provides promise-based variants of net's one-shot lifecycle
operations:

- `connect()` returns a promise that fulfills with a connected Socket
  once the 'connect' event fires, and rejects on connection failure or
  when an optional AbortSignal is aborted, destroying the socket.
- `listen()` creates a server, begins listening, and returns a promise
  that fulfills with the listening Server once the 'listening' event
  fires. It rejects if the server fails to bind or an optional
  AbortSignal is aborted, closing the server.

The functions are named for the actions they await, forming a parallel
`connect()`/`listen()` pair. This is intentionally not the callback
API's factory taxonomy: there `createConnection()` is canonical because
it both creates a socket and initiates connecting, whereas
`createServer()` is a pure factory that does not listen. A
`createConnection`/`createServer` pairing would be inconsistent here
because `createServer` has no completion to await, so no
`createConnection` alias is provided.

Refs: nodejs#21482
Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ethan Arrowood <ethan@arrowood.dev>
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/net

@Ethan-Arrowood Ethan-Arrowood added net Issues and PRs related to the net subsystem. semver-minor PRs that contain new features and should be released in the next minor version. labels Jun 17, 2026
@nodejs-github-bot nodejs-github-bot added the needs-ci PRs that need a full CI run. label Jun 17, 2026
@Ethan-Arrowood Ethan-Arrowood added the experimental Issues and PRs related to experimental features. label Jun 17, 2026

@pimterry pimterry left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the existing tests is unhappy, but otherwise this looks great to me ❤️

I'd be OK to ship experimental as-is, one note to consider: normal netServer.listen({ signal }) takes a signal which can be used to abort the server any time, but netPromises.listen({ signal }) takes a signal that can be used to cancel only the initial listen, and does nothing later on. The signal is linked only to the once(server, 'listening'... and is dropped from the server itself.

There's a specific comment confirming this is intentional, but then the docs here slightly disagree... I can see arguments both ways: this approach is more clearly linked to the promise flow, but then it does create an inconsistency between the APIs.

I'm happy either way tbh, just food for thought.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

experimental Issues and PRs related to experimental features. needs-ci PRs that need a full CI run. net Issues and PRs related to the net subsystem. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants