GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — net: add experimental net/promises API · nodejs/node@b6e7935

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • doc/api
    • net.md
  • lib
    • internal/net
      • promises.js
    • net.js
    • net
      • promises.js
  • test/parallel
    • test-net-promises-connect.js
    • test-net-promises-listen.js
    • test-net-server-async-iterator.js
    • test-repl-tab-complete-import.js
    • test-repl-tab-complete-require.js
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/net.md‎

Copy file name to clipboardExpand all lines: doc/api/net.md
+119Lines changed: 119 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,48 @@ changes:
452452
Calls [`server.close()`][] and returns a promise that fulfills when the
453453
server has closed.
454454

455+
### `server[Symbol.asyncIterator]()`
456+
457+
<!-- YAML
458+
added: REPLACEME
459+
-->
460+
461+
> Stability: 1 - Experimental
462+
463+
* Returns: {AsyncIterator} An async iterator that yields each incoming
464+
[`net.Socket`][].
465+
466+
Returns an async iterator over the server's incoming connections, allowing them
467+
to be consumed with `for await...of` as an alternative to the [`'connection'`][]
468+
event. Iteration ends when the server emits [`'close'`][], and rejects if the
469+
server emits [`'error'`][].
470+
471+
The loop only advances to the next connection once the current iteration's body
472+
has finished awaiting, so connection handling should be dispatched to a separate
473+
async task rather than awaited inline. Otherwise connections are serialized:
474+
each one waits for the previous to be fully handled.
475+
476+
```mjs
477+
import { createServer } from 'node:net';
478+
479+
const server = createServer().listen(8124);
480+
481+
async function handleConnection(socket) {
482+
// ...handle the connection, awaiting as needed.
483+
socket.end('hello world!');
484+
}
485+
486+
for await (const socket of server) {
487+
// Dispatch handling to a separate task so the loop keeps accepting
488+
// connections instead of serializing them.
489+
handleConnection(socket);
490+
}
491+
```
492+
493+
The server does not stop accepting connections while the loop body runs, so a
494+
consumer slower than the connection rate can buffer them without bound. Use
495+
[`server.maxConnections`][] to bound concurrency.
496+
455497
### `server.getConnections(callback)`
456498

457499
<!-- YAML
@@ -2276,6 +2318,82 @@ net.isIPv6('::1'); // returns true
22762318
net.isIPv6('fhqwhgads'); // returns false
22772319
```
22782320

2321+
## `net/promises` API
2322+
2323+
<!-- YAML
2324+
added: REPLACEME
2325+
-->
2326+
2327+
> Stability: 1 - Experimental
2328+
2329+
The `net/promises` API provides a set of `net` functions that return `Promise`
2330+
objects rather than relying on events. The API is accessible via
2331+
`require('node:net').promises` or `require('node:net/promises')`.
2332+
2333+
### `netPromises.connect(options)`
2334+
2335+
### `netPromises.connect(path)`
2336+
2337+
### `netPromises.connect(port[, host])`
2338+
2339+
<!-- YAML
2340+
added: REPLACEME
2341+
-->
2342+
2343+
* `options` {Object} Accepts the same arguments as [`net.connect()`][]. May
2344+
include a `signal` {AbortSignal} that can be used to abort an in-progress
2345+
connection attempt.
2346+
* Returns: {Promise} Fulfills with a connected [`net.Socket`][].
2347+
2348+
A promise-based alternative to [`net.connect()`][]. The returned promise is
2349+
fulfilled with the socket once its [`'connect'`][] event fires, and is rejected
2350+
if the connection fails or the `signal` is aborted. When the promise rejects,
2351+
the underlying socket is destroyed.
2352+
2353+
This API is named for the action it performs and awaits — connecting — to
2354+
parallel [`netPromises.listen()`][]. It is not named `createConnection()`,
2355+
because that name belongs to the socket-factory taxonomy of the callback API,
2356+
which has no counterpart here.
2357+
2358+
```mjs
2359+
import { connect } from 'node:net/promises';
2360+
2361+
const socket = await connect({ port: 8124 });
2362+
socket.write('hello world!');
2363+
socket.end();
2364+
```
2365+
2366+
### `netPromises.listen([options])`
2367+
2368+
<!-- YAML
2369+
added: REPLACEME
2370+
-->
2371+
2372+
* `options` {Object} Accepts the same options as [`net.createServer()`][] and
2373+
[`server.listen()`][], plus:
2374+
* `connectionListener` {Function} Automatically set as a listener for the
2375+
[`'connection'`][] event.
2376+
* `signal` {AbortSignal} An `AbortSignal` that may be used to abort the
2377+
server. Aborting before the server is listening rejects the returned
2378+
promise with an `AbortError`; aborting at any later point closes the
2379+
server, matching the `signal` option of [`server.listen()`][].
2380+
* Returns: {Promise} Fulfills with a listening [`net.Server`][].
2381+
2382+
Creates a [`net.Server`][] and begins listening. The returned promise is
2383+
fulfilled with the server once its [`'listening'`][] event fires, and is
2384+
rejected if the server fails to bind or the `signal` is aborted before it is
2385+
listening. When the promise rejects, the server is closed.
2386+
2387+
The resolved server is async iterable, so incoming connections can be consumed
2388+
with `for await...of` (see `server[Symbol.asyncIterator]()`).
2389+
2390+
```mjs
2391+
import { listen } from 'node:net/promises';
2392+
2393+
const server = await listen({ port: 8124 });
2394+
console.log('listening on', server.address().port);
2395+
```
2396+
22792397
[IPC]: #ipc-support
22802398
[Identifying paths for IPC connections]: #identifying-paths-for-ipc-connections
22812399
[RFC 8305]: https://www.rfc-editor.org/rfc/rfc8305.txt
@@ -2310,6 +2428,7 @@ net.isIPv6('fhqwhgads'); // returns false
23102428
[`net.createServer()`]: #netcreateserveroptions-connectionlistener
23112429
[`net.getDefaultAutoSelectFamily()`]: #netgetdefaultautoselectfamily
23122430
[`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: #netgetdefaultautoselectfamilyattempttimeout
2431+
[`netPromises.listen()`]: #netpromiseslistenoptions
23132432
[`new net.Socket(options)`]: #new-netsocketoptions
23142433
[`readable.setEncoding()`]: stream.md#readablesetencodingencoding
23152434
[`server.address()`]: #serveraddress
Collapse file

‎lib/internal/net/promises.js‎

Copy file name to clipboard
+88Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
'use strict';
2+
3+
const { once } = require('events');
4+
const {
5+
validateAbortSignal,
6+
validateObject,
7+
} = require('internal/validators');
8+
const { kEmptyObject } = require('internal/util');
9+
10+
// Lazily loaded to avoid a require cycle with the `net` module, which exposes
11+
// this namespace through its `promises` getter.
12+
let net;
13+
function lazyNet() {
14+
net ??= require('net');
15+
return net;
16+
}
17+
18+
// Resolves with a connected `net.Socket` once the `'connect'` event fires, and
19+
// rejects if the connection fails or the optional `signal` is aborted.
20+
async function connect(...args) {
21+
const lazy = lazyNet();
22+
const options = lazy._normalizeArgs(args)[0];
23+
const { signal } = options;
24+
if (signal !== undefined) {
25+
validateAbortSignal(signal, 'options.signal');
26+
signal.throwIfAborted();
27+
}
28+
29+
// Strip the signal so the socket does not also install its own abort
30+
// handling; rejecting and destroying below fully tears the socket down.
31+
const socket = lazy.connect({ ...options, signal: undefined });
32+
33+
try {
34+
await once(socket, 'connect', signal !== undefined ? { signal } : kEmptyObject);
35+
} catch (err) {
36+
socket.destroy();
37+
throw err;
38+
}
39+
return socket;
40+
}
41+
42+
// Creates a server and resolves with it once it is listening, rejecting if it
43+
// fails to bind or the optional `signal` is aborted.
44+
async function listen(options = kEmptyObject) {
45+
validateObject(options, 'options');
46+
const { signal, connectionListener } = options;
47+
if (signal !== undefined) {
48+
validateAbortSignal(signal, 'options.signal');
49+
signal.throwIfAborted();
50+
}
51+
52+
const lazy = lazyNet();
53+
const server = lazy.createServer(options, connectionListener);
54+
55+
try {
56+
// Default to an ephemeral port when no listen target is supplied, matching
57+
// `server.listen()` with no arguments; passing a target-less options object
58+
// (the `{}` default, or e.g. `{ signal }`) straight to listen() would throw
59+
// ERR_INVALID_ARG_VALUE. `signal` is passed through so net installs its own
60+
// close-on-abort handler: the signal aborts the server for its entire
61+
// lifetime, not just the pending listen.
62+
const hasListenTarget = options.port !== undefined ||
63+
options.path !== undefined ||
64+
options.fd !== undefined ||
65+
options.handle !== undefined;
66+
server.listen(hasListenTarget ? options : { ...options, port: 0 });
67+
await once(server, 'listening', signal !== undefined ? { signal } : kEmptyObject);
68+
} catch (err) {
69+
// On abort, net's signal handler already closes the server, so closing
70+
// again would be redundant; on other failures (e.g. a bind error) there
71+
// is no such handler, so close it here.
72+
if (!signal?.aborted) {
73+
server.close();
74+
}
75+
throw err;
76+
}
77+
return server;
78+
}
79+
80+
module.exports = {
81+
connect,
82+
listen,
83+
get isIP() { return lazyNet().isIP; },
84+
get isIPv4() { return lazyNet().isIPv4; },
85+
get isIPv6() { return lazyNet().isIPv6; },
86+
get BlockList() { return lazyNet().BlockList; },
87+
get SocketAddress() { return lazyNet().SocketAddress; },
88+
};
Collapse file

‎lib/net.js‎

Copy file name to clipboardExpand all lines: lib/net.js
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const {
3737
ObjectSetPrototypeOf,
3838
Symbol,
3939
SymbolAsyncDispose,
40+
SymbolAsyncIterator,
4041
SymbolDispose,
4142
} = primordials;
4243

@@ -156,6 +157,8 @@ let cluster;
156157
let dns;
157158
let BlockList;
158159
let SocketAddress;
160+
let netPromises;
161+
let kFirstEventParam;
159162
let autoSelectFamilyDefault = getOptionValue('--network-family-autoselection');
160163
let autoSelectFamilyAttemptTimeoutDefault = getOptionValue('--network-family-autoselection-attempt-timeout');
161164

@@ -2854,6 +2857,14 @@ Server.prototype[SymbolAsyncDispose] = async function() {
28542857
await FunctionPrototypeCall(promisify(this.close), this);
28552858
};
28562859

2860+
Server.prototype[SymbolAsyncIterator] = function() {
2861+
kFirstEventParam ??= require('internal/events/symbols').kFirstEventParam;
2862+
return EventEmitter.on(this, 'connection', {
2863+
close: ['close'],
2864+
[kFirstEventParam]: true,
2865+
});
2866+
};
2867+
28572868
Server.prototype._emitCloseIfDrained = function() {
28582869
debug('SERVER _emitCloseIfDrained');
28592870

@@ -2945,6 +2956,10 @@ module.exports = {
29452956
connect,
29462957
createConnection: connect,
29472958
createServer,
2959+
get promises() {
2960+
netPromises ??= require('internal/net/promises');
2961+
return netPromises;
2962+
},
29482963
isIP: isIP,
29492964
isIPv4: isIPv4,
29502965
isIPv6: isIPv6,
Collapse file

‎lib/net/promises.js‎

Copy file name to clipboard
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
module.exports = require('internal/net/promises');
Collapse file

‎test/parallel/test-net-promises-connect.js‎

Copy file name to clipboard
+62Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const net = require('net');
5+
const { once } = require('events');
6+
const { connect } = require('net/promises');
7+
8+
(async () => {
9+
// Resolves with a connected socket and round-trips data.
10+
{
11+
const server = net.createServer((socket) => {
12+
socket.end('hello');
13+
}).listen(0);
14+
await once(server, 'listening');
15+
const socket = await connect({ port: server.address().port });
16+
assert.strictEqual(socket.connecting, false);
17+
const chunks = [];
18+
for await (const chunk of socket) {
19+
chunks.push(chunk);
20+
}
21+
assert.strictEqual(Buffer.concat(chunks).toString(), 'hello');
22+
server.close();
23+
}
24+
25+
// net.promises is the same object as require('net/promises').
26+
assert.strictEqual(net.promises, require('net/promises'));
27+
28+
// Rejects when the connection is refused.
29+
{
30+
const server = net.createServer().listen(0);
31+
await once(server, 'listening');
32+
const { port } = server.address();
33+
server.close();
34+
await once(server, 'close');
35+
await assert.rejects(connect({ port }), { code: 'ECONNREFUSED' });
36+
}
37+
38+
// A pre-aborted signal rejects with an AbortError.
39+
{
40+
await assert.rejects(
41+
connect({ port: 0, signal: AbortSignal.abort() }),
42+
{ name: 'AbortError' });
43+
}
44+
45+
// Aborting while connecting rejects with an AbortError.
46+
{
47+
const server = net.createServer().listen(0);
48+
await once(server, 'listening');
49+
const controller = new AbortController();
50+
const promise = connect({ port: server.address().port, signal: controller.signal });
51+
controller.abort();
52+
await assert.rejects(promise, { name: 'AbortError' });
53+
server.close();
54+
}
55+
56+
// An invalid signal throws.
57+
{
58+
await assert.rejects(
59+
connect({ port: 0, signal: 'INVALID_SIGNAL' }),
60+
{ code: 'ERR_INVALID_ARG_TYPE' });
61+
}
62+
})().then(common.mustCall());

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.