Skip to content

Commit 47b979d

Browse files
feat: add promise-based acknowledgements
This commit adds some syntactic sugar around acknowledgements: ```js // without timeout const response = await socket.emitWithAck("hello", "world"); // with a specific timeout try { const response = await socket.timeout(1000).emitWithAck("hello", "world"); } catch (err) { // the server did not acknowledge the event in the given delay } ``` Note: enviroments that do not support Promises ([1]) will need to add a polyfill in order to use this feature See also: socketio/socket.io@184f3cf [1]: https://caniuse.com/promises
1 parent b4e20c5 commit 47b979d

File tree

3 files changed

+123
-1
lines changed

3 files changed

+123
-1
lines changed

lib/socket.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ export type DecorateAcknowledgements<E> = {
4343
: E[K];
4444
};
4545

46+
export type Last<T extends any[]> = T extends [...infer H, infer L] ? L : any;
47+
export type AllButLast<T extends any[]> = T extends [...infer H, infer L]
48+
? H
49+
: any[];
50+
export type FirstArg<T> = T extends (arg: infer Param) => infer Result
51+
? Param
52+
: any;
53+
4654
export interface SocketOptions {
4755
/**
4856
* the authentication payload sent when connecting to the Namespace
@@ -407,6 +415,40 @@ export class Socket<
407415
};
408416
}
409417

418+
/**
419+
* Emits an event and waits for an acknowledgement
420+
*
421+
* @example
422+
* // without timeout
423+
* const response = await socket.emitWithAck("hello", "world");
424+
*
425+
* // with a specific timeout
426+
* try {
427+
* const response = await socket.timeout(1000).emitWithAck("hello", "world");
428+
* } catch (err) {
429+
* // the server did not acknowledge the event in the given delay
430+
* }
431+
*
432+
* @return a Promise that will be fulfilled when the server acknowledges the event
433+
*/
434+
public emitWithAck<Ev extends EventNames<EmitEvents>>(
435+
ev: Ev,
436+
...args: AllButLast<EventParams<EmitEvents, Ev>>
437+
): Promise<FirstArg<Last<EventParams<EmitEvents, Ev>>>> {
438+
// the timeout flag is optional
439+
const withErr = this.flags.timeout !== undefined;
440+
return new Promise((resolve, reject) => {
441+
args.push((arg1, arg2) => {
442+
if (withErr) {
443+
return arg1 ? reject(arg1) : resolve(arg2);
444+
} else {
445+
return resolve(arg1);
446+
}
447+
});
448+
this.emit(ev, ...(args as any[] as EventParams<EmitEvents, Ev>));
449+
});
450+
}
451+
410452
/**
411453
* Sends a packet.
412454
*

test/socket.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,17 @@ describe("socket", () => {
330330
});
331331
});
332332

333+
it("should emit an event and wait for the acknowledgement", () => {
334+
return wrap(async (done) => {
335+
const socket = io(BASE_URL, { forceNew: true });
336+
337+
const val = await socket.emitWithAck("echo", 123);
338+
expect(val).to.be(123);
339+
340+
success(done, socket);
341+
});
342+
});
343+
333344
describe("volatile packets", () => {
334345
it("should discard a volatile packet when the socket is not connected", () => {
335346
return wrap((done) => {
@@ -561,5 +572,32 @@ describe("socket", () => {
561572
});
562573
});
563574
});
575+
576+
it("should timeout when the server does not acknowledge the event (promise)", () => {
577+
return wrap(async (done) => {
578+
const socket = io(BASE_URL + "/");
579+
580+
try {
581+
await socket.timeout(50).emitWithAck("unknown");
582+
expect.fail();
583+
} catch (e) {
584+
success(done, socket);
585+
}
586+
});
587+
});
588+
589+
it("should not timeout when the server does acknowledge the event (promise)", () => {
590+
return wrap(async (done) => {
591+
const socket = io(BASE_URL + "/");
592+
593+
try {
594+
const value = await socket.timeout(50).emitWithAck("echo", 42);
595+
expect(value).to.be(42);
596+
success(done, socket);
597+
} catch (e) {
598+
expect.fail();
599+
}
600+
});
601+
});
564602
});
565603
});

test/typed-events.test-d.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { io, Socket } from "..";
22
import type { DefaultEventsMap } from "@socket.io/component-emitter";
33
import { expectError, expectType } from "tsd";
4+
import { createServer } from "http";
45

56
// This file is run by tsd, not mocha.
67

@@ -54,7 +55,7 @@ describe("typed events", () => {
5455
});
5556

5657
describe("emit", () => {
57-
it("accepts any parameters", () => {
58+
it("accepts any parameters", async () => {
5859
const socket = io();
5960

6061
socket.emit("random", 1, "2", [3]);
@@ -72,6 +73,24 @@ describe("typed events", () => {
7273
});
7374
});
7475
});
76+
77+
describe("emitWithAck", () => {
78+
it("accepts any parameters", async () => {
79+
const socket = io();
80+
81+
const value = await socket.emitWithAck(
82+
"ackFromClientSingleArg",
83+
"1",
84+
2
85+
);
86+
expectType<any>(value);
87+
88+
const value2 = await socket
89+
.timeout(1000)
90+
.emitWithAck("ackFromClientSingleArg", "3", 4);
91+
expectType<any>(value2);
92+
});
93+
});
7594
});
7695

7796
describe("single event map", () => {
@@ -127,6 +146,11 @@ describe("typed events", () => {
127146
b: number,
128147
ack: (c: string, d: boolean) => void
129148
) => void;
149+
ackFromClientSingleArg: (
150+
a: string,
151+
b: number,
152+
ack: (c: string) => void
153+
) => void;
130154
ackFromClientNoArg: (ack: () => void) => void;
131155
}
132156

@@ -189,5 +213,23 @@ describe("typed events", () => {
189213
expectError(socket.emit("wrong name"));
190214
});
191215
});
216+
217+
describe("emitWithAck", () => {
218+
it("accepts arguments of the correct types", async () => {
219+
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io();
220+
221+
const value = await socket.emitWithAck(
222+
"ackFromClientSingleArg",
223+
"1",
224+
2
225+
);
226+
expectType<string>(value);
227+
228+
const value2 = await socket
229+
.timeout(1000)
230+
.emitWithAck("ackFromClientSingleArg", "3", 4);
231+
expectType<string>(value2);
232+
});
233+
});
192234
});
193235
});

0 commit comments

Comments
 (0)