@bytecodealliance/preview2-shim 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,294 @@
1
+ import { checkedU64AsNumber } from "./common.js";
2
+ import { inputStreamCreate, outputStreamCreate, pollableCreate } from "./io.js";
3
+ const symbolDispose = Symbol.dispose || Symbol.for("dispose");
4
+ const unsupported = () => {
5
+ throw "not-supported";
6
+ };
7
+ function addressKey(address) {
8
+ return `${address.tag}:${address.val.address.join(".")}:${address.val.port}`;
9
+ }
10
+ function takeBytes(chunks, length) {
11
+ const chunk = chunks.shift();
12
+ if (!chunk) {
13
+ throw { tag: "closed" };
14
+ }
15
+ const requested = checkedU64AsNumber(length, "length");
16
+ if (chunk.byteLength <= requested) {
17
+ return chunk;
18
+ }
19
+ chunks.unshift(chunk.subarray(requested));
20
+ return chunk.subarray(0, requested);
21
+ }
22
+ /** The browser-side endpoint of an in-memory TCP connection. */
23
+ export class InMemoryTcpClient {
24
+ #toServer = [];
25
+ #fromServer = [];
26
+ write(bytes) {
27
+ this.#toServer.push(new Uint8Array(bytes));
28
+ }
29
+ read(length = 4096n) {
30
+ return takeBytes(this.#fromServer, length);
31
+ }
32
+ _serverStreams() {
33
+ return [
34
+ inputStreamCreate({ blockingRead: (length) => takeBytes(this.#toServer, length) }),
35
+ outputStreamCreate({
36
+ write: (bytes) => this.#fromServer.push(new Uint8Array(bytes)),
37
+ }),
38
+ ];
39
+ }
40
+ }
41
+ /** Deterministic, process-local TCP namespaces with a browser-side client API. */
42
+ export class InMemoryTcpSockets {
43
+ #pending = new Map();
44
+ tcp;
45
+ tcpCreateSocket;
46
+ constructor() {
47
+ const pending = this.#pending;
48
+ class TcpSocket {
49
+ #family;
50
+ #localAddress;
51
+ #remoteAddress;
52
+ #listening = false;
53
+ constructor(family) {
54
+ this.#family = family;
55
+ }
56
+ startBind(_network, localAddress) {
57
+ this.#localAddress = localAddress;
58
+ }
59
+ finishBind() {
60
+ if (!this.#localAddress) {
61
+ throw "not-in-progress";
62
+ }
63
+ }
64
+ startConnect = unsupported;
65
+ finishConnect = unsupported;
66
+ startListen() {
67
+ if (!this.#localAddress) {
68
+ throw "invalid-state";
69
+ }
70
+ }
71
+ finishListen() {
72
+ this.#listening = true;
73
+ }
74
+ accept() {
75
+ if (!this.#listening || !this.#localAddress) {
76
+ throw "invalid-state";
77
+ }
78
+ const client = pending.get(addressKey(this.#localAddress))?.shift();
79
+ if (!client) {
80
+ throw "would-block";
81
+ }
82
+ const connected = new TcpSocket(this.#family);
83
+ connected.#localAddress = this.#localAddress;
84
+ const [input, output] = client._serverStreams();
85
+ return [connected, input, output];
86
+ }
87
+ localAddress() {
88
+ if (!this.#localAddress) {
89
+ throw "invalid-state";
90
+ }
91
+ return this.#localAddress;
92
+ }
93
+ remoteAddress() {
94
+ if (!this.#remoteAddress) {
95
+ throw "invalid-state";
96
+ }
97
+ return this.#remoteAddress;
98
+ }
99
+ isListening() {
100
+ return this.#listening;
101
+ }
102
+ addressFamily() {
103
+ return this.#family;
104
+ }
105
+ setListenBacklogSize = unsupported;
106
+ keepAliveEnabled = unsupported;
107
+ setKeepAliveEnabled = unsupported;
108
+ keepAliveIdleTime = unsupported;
109
+ setKeepAliveIdleTime = unsupported;
110
+ keepAliveInterval = unsupported;
111
+ setKeepAliveInterval = unsupported;
112
+ keepAliveCount = unsupported;
113
+ setKeepAliveCount = unsupported;
114
+ hopLimit = unsupported;
115
+ setHopLimit = unsupported;
116
+ receiveBufferSize = unsupported;
117
+ setReceiveBufferSize = unsupported;
118
+ sendBufferSize = unsupported;
119
+ setSendBufferSize = unsupported;
120
+ subscribe() {
121
+ return pollableCreate();
122
+ }
123
+ shutdown() { }
124
+ [symbolDispose]() { }
125
+ }
126
+ this.tcp = { TcpSocket };
127
+ this.tcpCreateSocket = {
128
+ createTcpSocket: (family) => new TcpSocket(family),
129
+ };
130
+ }
131
+ connect(serverAddress) {
132
+ const client = new InMemoryTcpClient();
133
+ const key = addressKey(serverAddress);
134
+ const clients = this.#pending.get(key);
135
+ if (clients) {
136
+ clients.push(client);
137
+ }
138
+ else {
139
+ this.#pending.set(key, [client]);
140
+ }
141
+ return client;
142
+ }
143
+ }
144
+ /** The browser-side endpoint of an in-memory UDP socket. */
145
+ export class InMemoryUdpClient {
146
+ #send;
147
+ #received = [];
148
+ constructor(send) {
149
+ this.#send = send;
150
+ }
151
+ send(bytes, serverAddress) {
152
+ this.#send(bytes, serverAddress);
153
+ }
154
+ read() {
155
+ return takeBytes(this.#received, 65535n);
156
+ }
157
+ _receive(bytes) {
158
+ this.#received.push(new Uint8Array(bytes));
159
+ }
160
+ }
161
+ /** Deterministic, process-local UDP namespaces with a browser-side client API. */
162
+ export class InMemoryUdpSockets {
163
+ #datagrams = new Map();
164
+ #clients = new Map();
165
+ udp;
166
+ udpCreateSocket;
167
+ constructor() {
168
+ const datagrams = this.#datagrams;
169
+ const clients = this.#clients;
170
+ class IncomingDatagramStream {
171
+ queue;
172
+ constructor(queue) {
173
+ this.queue = queue;
174
+ }
175
+ receive(maxResults) {
176
+ return this.queue.splice(0, checkedU64AsNumber(maxResults, "max results"));
177
+ }
178
+ subscribe() {
179
+ return pollableCreate();
180
+ }
181
+ [symbolDispose]() { }
182
+ }
183
+ class OutgoingDatagramStream {
184
+ remoteAddress;
185
+ #permit = 0;
186
+ constructor(remoteAddress) {
187
+ this.remoteAddress = remoteAddress;
188
+ }
189
+ checkSend() {
190
+ this.#permit = 1_024;
191
+ return 1024n;
192
+ }
193
+ send(outgoing) {
194
+ if (outgoing.length > this.#permit) {
195
+ throw new TypeError("datagram count exceeds the permit returned by checkSend");
196
+ }
197
+ this.#permit -= outgoing.length;
198
+ for (const datagram of outgoing) {
199
+ const destination = datagram.remoteAddress ?? this.remoteAddress;
200
+ if (!destination) {
201
+ throw "invalid-argument";
202
+ }
203
+ const client = clients.get(addressKey(destination));
204
+ if (!client) {
205
+ throw "remote-unreachable";
206
+ }
207
+ client._receive(datagram.data);
208
+ }
209
+ return BigInt(outgoing.length);
210
+ }
211
+ subscribe() {
212
+ return pollableCreate();
213
+ }
214
+ [symbolDispose]() { }
215
+ }
216
+ class UdpSocket {
217
+ #family;
218
+ #localAddress;
219
+ #remoteAddress;
220
+ constructor(family) {
221
+ this.#family = family;
222
+ }
223
+ startBind(_network, localAddress) {
224
+ this.#localAddress = localAddress;
225
+ }
226
+ finishBind() {
227
+ if (!this.#localAddress) {
228
+ throw "not-in-progress";
229
+ }
230
+ }
231
+ stream(remoteAddress) {
232
+ if (!this.#localAddress) {
233
+ throw "invalid-state";
234
+ }
235
+ this.#remoteAddress = remoteAddress;
236
+ const key = addressKey(this.#localAddress);
237
+ let queue = datagrams.get(key);
238
+ if (!queue) {
239
+ queue = [];
240
+ datagrams.set(key, queue);
241
+ }
242
+ return [
243
+ new IncomingDatagramStream(queue),
244
+ new OutgoingDatagramStream(remoteAddress),
245
+ ];
246
+ }
247
+ localAddress() {
248
+ if (!this.#localAddress) {
249
+ throw "invalid-state";
250
+ }
251
+ return this.#localAddress;
252
+ }
253
+ remoteAddress() {
254
+ return this.#remoteAddress;
255
+ }
256
+ addressFamily() {
257
+ return this.#family;
258
+ }
259
+ unicastHopLimit = unsupported;
260
+ setUnicastHopLimit = unsupported;
261
+ receiveBufferSize = unsupported;
262
+ setReceiveBufferSize = unsupported;
263
+ sendBufferSize = unsupported;
264
+ setSendBufferSize = unsupported;
265
+ subscribe() {
266
+ return pollableCreate();
267
+ }
268
+ [symbolDispose]() { }
269
+ }
270
+ this.udp = {
271
+ IncomingDatagramStream,
272
+ OutgoingDatagramStream,
273
+ UdpSocket,
274
+ };
275
+ this.udpCreateSocket = {
276
+ createUdpSocket: (family) => new UdpSocket(family),
277
+ };
278
+ }
279
+ createClient(localAddress) {
280
+ const client = new InMemoryUdpClient((bytes, serverAddress) => {
281
+ const key = addressKey(serverAddress);
282
+ const queue = this.#datagrams.get(key);
283
+ const incoming = { data: new Uint8Array(bytes), remoteAddress: localAddress };
284
+ if (queue) {
285
+ queue.push(incoming);
286
+ }
287
+ else {
288
+ this.#datagrams.set(key, [incoming]);
289
+ }
290
+ });
291
+ this.#clients.set(addressKey(localAddress), client);
292
+ return client;
293
+ }
294
+ }
@@ -8,13 +8,19 @@ type IOutputStream = StreamsNamespace.OutputStream;
8
8
  export type InputStreamHandler = Partial<IInputStream> & Required<Pick<IInputStream, "blockingRead">> & {
9
9
  drop?: () => void;
10
10
  };
11
+ export interface PollableSource {
12
+ ready(): boolean;
13
+ wait(): Promise<void>;
14
+ }
11
15
  /**
12
16
  * Handler interface for creating custom output streams
13
17
  */
14
18
  export type OutputStreamHandler = Partial<IOutputStream> & Required<Pick<IOutputStream, "write">> & {
15
19
  drop?: () => void;
16
20
  };
21
+ export declare const ioErrorCreate: (message: string) => ErrorNamespace.Error;
17
22
  declare class InputStream implements IInputStream {
23
+ #private;
18
24
  [symbolDispose]: () => void;
19
25
  id: number;
20
26
  handler: InputStreamHandler;
@@ -27,6 +33,7 @@ declare class InputStream implements IInputStream {
27
33
  }
28
34
  export declare const inputStreamCreate: typeof InputStream._create;
29
35
  declare class OutputStream implements IOutputStream {
36
+ #private;
30
37
  [symbolDispose]: () => void;
31
38
  id: number;
32
39
  open: boolean;
@@ -40,7 +47,7 @@ declare class OutputStream implements IOutputStream {
40
47
  writeZeroes(len: bigint): void;
41
48
  blockingWriteZeroesAndFlush(len: bigint): void;
42
49
  splice(src: InputStream, len: bigint): bigint;
43
- blockingSplice(_src: InputStream, _len: bigint): bigint;
50
+ blockingSplice(src: InputStream, len: bigint): bigint;
44
51
  subscribe(): PollNamespace.Pollable | Pollable;
45
52
  }
46
53
  export declare const outputStreamCreate: typeof OutputStream._create;
@@ -49,9 +56,11 @@ export declare const streams: typeof StreamsNamespace;
49
56
  declare class Pollable implements PollNamespace.Pollable {
50
57
  #private;
51
58
  [symbolDispose]: () => void;
52
- static _create(promise?: Promise<void>): Pollable;
59
+ static _create(source?: Promise<void> | PollableSource): Pollable;
53
60
  ready(): boolean;
54
61
  block(): Promise<void>;
62
+ _onDispose(callback: () => void): void;
63
+ _invalidate(): void;
55
64
  }
56
65
  export declare const pollableCreate: typeof Pollable._create;
57
66
  export declare const poll: typeof PollNamespace;
@@ -1,13 +1,21 @@
1
+ import { checkedU64AsNumber } from "./common.js";
1
2
  let id = 0;
2
3
  const symbolDispose = Symbol.dispose || Symbol.for("dispose");
4
+ const checkedLength = (len, name = "length") => checkedU64AsNumber(len, name);
5
+ function closed() {
6
+ throw { tag: "closed" };
7
+ }
3
8
  class IoError extends Error {
4
9
  toDebugString() {
5
10
  return this.message;
6
11
  }
7
12
  }
13
+ export const ioErrorCreate = (message) => new IoError(message);
8
14
  class InputStream {
9
15
  id;
10
16
  handler;
17
+ #open = true;
18
+ #children = new Set();
11
19
  static _create(handler) {
12
20
  const stream = new InputStream();
13
21
  if (!handler) {
@@ -18,15 +26,27 @@ class InputStream {
18
26
  return stream;
19
27
  }
20
28
  read(len) {
29
+ checkedLength(len);
30
+ if (!this.#open) {
31
+ closed();
32
+ }
21
33
  if (this.handler.read) {
22
- return this.handler.read(len);
34
+ return this.handler.read.call(this, len);
23
35
  }
24
36
  return this.handler.blockingRead.call(this, len);
25
37
  }
26
38
  blockingRead(len) {
39
+ checkedLength(len);
40
+ if (!this.#open) {
41
+ closed();
42
+ }
27
43
  return this.handler.blockingRead.call(this, len);
28
44
  }
29
45
  skip(len) {
46
+ checkedLength(len);
47
+ if (!this.#open) {
48
+ closed();
49
+ }
30
50
  if (this.handler.skip) {
31
51
  return this.handler.skip.call(this, len);
32
52
  }
@@ -37,6 +57,10 @@ class InputStream {
37
57
  return this.blockingSkip.call(this, len);
38
58
  }
39
59
  blockingSkip(len) {
60
+ checkedLength(len);
61
+ if (!this.#open) {
62
+ closed();
63
+ }
40
64
  if (this.handler.blockingSkip) {
41
65
  return this.handler.blockingSkip.call(this, len);
42
66
  }
@@ -44,12 +68,27 @@ class InputStream {
44
68
  return BigInt(bytes.byteLength);
45
69
  }
46
70
  subscribe() {
47
- if (this.handler.subscribe) {
48
- return this.handler.subscribe();
71
+ if (!this.#open) {
72
+ return pollableCreate();
73
+ }
74
+ const pollable = this.handler.subscribe
75
+ ? this.handler.subscribe.call(this)
76
+ : pollableCreate();
77
+ if (pollable instanceof Pollable) {
78
+ this.#children.add(pollable);
79
+ pollable._onDispose(() => this.#children.delete(pollable));
49
80
  }
50
- return new Pollable();
81
+ return pollable;
51
82
  }
52
83
  [symbolDispose]() {
84
+ if (!this.#open) {
85
+ return;
86
+ }
87
+ this.#open = false;
88
+ for (const child of this.#children) {
89
+ child._invalidate();
90
+ }
91
+ this.#children.clear();
53
92
  if (this.handler.drop) {
54
93
  this.handler.drop.call(this);
55
94
  }
@@ -62,6 +101,8 @@ class OutputStream {
62
101
  id;
63
102
  open;
64
103
  handler;
104
+ #permit = 0n;
105
+ #children = new Set();
65
106
  static _create(handler) {
66
107
  const stream = new OutputStream();
67
108
  if (!handler) {
@@ -74,56 +115,116 @@ class OutputStream {
74
115
  }
75
116
  checkWrite() {
76
117
  if (!this.open) {
77
- return 0n;
118
+ closed();
78
119
  }
79
120
  if (this.handler.checkWrite) {
80
- return this.handler.checkWrite.call(this);
121
+ const permit = this.handler.checkWrite.call(this);
122
+ checkedLength(permit, "write permit");
123
+ this.#permit = permit;
124
+ return permit;
81
125
  }
82
- return 1000000n;
126
+ this.#permit = 1000000n;
127
+ return this.#permit;
83
128
  }
84
129
  write(buf) {
130
+ if (!this.open) {
131
+ closed();
132
+ }
133
+ if (BigInt(buf.byteLength) > this.#permit) {
134
+ throw new Error("write exceeds the permit returned by checkWrite");
135
+ }
136
+ this.#permit -= BigInt(buf.byteLength);
85
137
  this.handler.write.call(this, buf);
86
138
  }
87
139
  blockingWriteAndFlush(buf) {
140
+ if (!this.open) {
141
+ closed();
142
+ }
143
+ if (buf.byteLength > 4096) {
144
+ throw new RangeError("blockingWriteAndFlush accepts at most 4096 bytes");
145
+ }
88
146
  if (this.handler.blockingWriteAndFlush) {
89
147
  return this.handler.blockingWriteAndFlush.call(this, buf);
90
148
  }
91
149
  this.handler.write.call(this, buf);
150
+ if (this.handler.blockingFlush) {
151
+ this.handler.blockingFlush.call(this);
152
+ }
153
+ else {
154
+ this.handler.flush?.call(this);
155
+ }
92
156
  }
93
157
  flush() {
158
+ if (!this.open) {
159
+ closed();
160
+ }
161
+ this.#permit = 0n;
94
162
  if (this.handler.flush) {
95
163
  this.handler.flush.call(this);
96
164
  }
97
165
  }
98
166
  blockingFlush() {
99
- this.open = true;
167
+ if (!this.open) {
168
+ closed();
169
+ }
100
170
  if (this.handler.blockingFlush) {
101
171
  this.handler.blockingFlush.call(this);
102
172
  }
173
+ else {
174
+ this.handler.flush?.call(this);
175
+ }
103
176
  }
104
177
  writeZeroes(len) {
105
- this.write.call(this, new Uint8Array(Number(len)));
178
+ const length = checkedLength(len);
179
+ if (len > this.#permit) {
180
+ throw new Error("write exceeds the permit returned by checkWrite");
181
+ }
182
+ this.write.call(this, new Uint8Array(length));
106
183
  }
107
184
  blockingWriteZeroesAndFlush(len) {
108
- this.blockingWriteAndFlush.call(this, new Uint8Array(Number(len)));
185
+ const length = checkedLength(len);
186
+ if (length > 4096) {
187
+ throw new RangeError("blockingWriteZeroesAndFlush accepts at most 4096 bytes");
188
+ }
189
+ this.blockingWriteAndFlush.call(this, new Uint8Array(length));
109
190
  }
110
191
  splice(src, len) {
111
- const spliceLen = Math.min(Number(len), Number(this.checkWrite.call(this)));
192
+ const spliceLen = Math.min(checkedLength(len), Number(this.checkWrite.call(this)));
112
193
  const bytes = src.read(BigInt(spliceLen));
113
194
  this.write.call(this, bytes);
114
195
  return BigInt(bytes.byteLength);
115
196
  }
116
- blockingSplice(_src, _len) {
117
- console.log(`[streams] Blocking splice ${this.id}`);
118
- return 0n;
197
+ blockingSplice(src, len) {
198
+ const spliceLen = Math.min(checkedLength(len), Number(this.checkWrite.call(this)));
199
+ const bytes = src.blockingRead(BigInt(spliceLen));
200
+ this.write.call(this, bytes);
201
+ return BigInt(bytes.byteLength);
119
202
  }
120
203
  subscribe() {
121
- if (this.handler.subscribe) {
122
- return this.handler.subscribe();
204
+ if (!this.open) {
205
+ return pollableCreate();
206
+ }
207
+ const pollable = this.handler.subscribe
208
+ ? this.handler.subscribe.call(this)
209
+ : pollableCreate();
210
+ if (pollable instanceof Pollable) {
211
+ this.#children.add(pollable);
212
+ pollable._onDispose(() => this.#children.delete(pollable));
213
+ }
214
+ return pollable;
215
+ }
216
+ [symbolDispose]() {
217
+ if (!this.open) {
218
+ return;
123
219
  }
124
- return new Pollable();
220
+ this.open = false;
221
+ this.#permit = 0n;
222
+ for (const child of this.#children) {
223
+ child._invalidate();
224
+ }
225
+ this.#children.clear();
226
+ this.handler.drop?.call(this);
125
227
  }
126
- [symbolDispose]() { }
127
228
  }
128
229
  export const outputStreamCreate = OutputStream._create;
129
230
  // @ts-expect-error - Deleting static method
@@ -133,33 +234,82 @@ export const error = {
133
234
  };
134
235
  export const streams = { InputStream, OutputStream };
135
236
  class Pollable {
136
- #ready = false;
137
- #promise = null;
138
- static _create(promise) {
237
+ #source = { ready: () => true, wait: () => Promise.resolve() };
238
+ #invalid = false;
239
+ #disposed = false;
240
+ #wait = null;
241
+ #disposeCallbacks = [];
242
+ #wakeUnusable;
243
+ #unusable = new Promise((resolve) => (this.#wakeUnusable = resolve));
244
+ static _create(source) {
139
245
  const pollable = new Pollable();
140
- if (!promise) {
141
- pollable.#ready = true;
142
- }
143
- else {
144
- pollable.#promise = promise.then(() => {
145
- pollable.#ready = true;
246
+ if (source instanceof Promise) {
247
+ let ready = false;
248
+ const wait = source.then(() => {
249
+ ready = true;
146
250
  }, () => {
147
- pollable.#ready = true;
251
+ ready = true;
148
252
  });
253
+ pollable.#source = { ready: () => ready, wait: () => wait };
254
+ }
255
+ else if (source) {
256
+ pollable.#source = source;
149
257
  }
150
258
  return pollable;
151
259
  }
152
260
  ready() {
153
- return this.#ready;
261
+ this.#assertUsable();
262
+ return this.#source.ready();
154
263
  }
155
264
  block() {
156
- if (this.#ready) {
265
+ this.#assertUsable();
266
+ if (this.#source.ready()) {
157
267
  return Promise.resolve();
158
268
  }
159
- return this.#promise || Promise.resolve();
269
+ // Deduplicate simultaneous waiters, but discard a completed wait so a
270
+ // level-triggered source can be polled again after its event is consumed.
271
+ if (!this.#wait) {
272
+ this.#wait = Promise.race([
273
+ Promise.resolve(this.#source.wait()),
274
+ this.#unusable.then(() => this.#assertUsable()),
275
+ ]).finally(() => {
276
+ this.#wait = null;
277
+ });
278
+ }
279
+ return this.#wait;
280
+ }
281
+ _onDispose(callback) {
282
+ if (this.#disposed) {
283
+ callback();
284
+ }
285
+ else {
286
+ this.#disposeCallbacks.push(callback);
287
+ }
288
+ }
289
+ _invalidate() {
290
+ if (this.#invalid || this.#disposed) {
291
+ return;
292
+ }
293
+ this.#invalid = true;
294
+ this.#wakeUnusable();
295
+ }
296
+ #assertUsable() {
297
+ if (this.#disposed) {
298
+ throw new Error("pollable has been disposed");
299
+ }
300
+ if (this.#invalid) {
301
+ throw new Error("pollable's parent resource has been disposed");
302
+ }
160
303
  }
161
304
  [symbolDispose]() {
162
- this.#promise = null;
305
+ if (this.#disposed) {
306
+ return;
307
+ }
308
+ this.#disposed = true;
309
+ this.#wakeUnusable();
310
+ for (const callback of this.#disposeCallbacks.splice(0)) {
311
+ callback();
312
+ }
163
313
  }
164
314
  }
165
315
  export const pollableCreate = Pollable._create;
@@ -179,19 +329,30 @@ function pollList(list) {
179
329
  }
180
330
  }
181
331
  if (ready.length > 0) {
182
- return new Uint32Array(ready);
332
+ // Browser guests commonly use an immediately-ready timer alongside an
333
+ // asynchronous Web API pollable. Yield a host task so Fetch, timers, and
334
+ // other event sources can progress instead of starving in a sync loop.
335
+ return new Promise((resolve) => setTimeout(() => {
336
+ const result = [];
337
+ for (let i = 0; i < list.length; i++) {
338
+ if (list[i].ready()) {
339
+ result.push(i);
340
+ }
341
+ }
342
+ resolve(new Uint32Array(result));
343
+ }, 0));
183
344
  }
184
345
  // None ready synchronously. Wait for the first to resolve via Promise.race,
185
346
  // then sweep for any others that became ready concurrently.
186
- return Promise.race(list.map((p, i) => p.block().then(() => {
187
- const result = [i];
188
- for (let j = 0; j < list.length; j++) {
189
- if (j !== i && list[j].ready()) {
190
- result.push(j);
347
+ return Promise.race(list.map((pollable) => pollable.block())).then(() => {
348
+ const result = [];
349
+ for (let i = 0; i < list.length; i++) {
350
+ if (list[i].ready()) {
351
+ result.push(i);
191
352
  }
192
353
  }
193
354
  return new Uint32Array(result);
194
- })));
355
+ });
195
356
  }
196
357
  function pollOne(poll) {
197
358
  return poll.block();