@@ -241,7 +241,33 @@ export class Socket<
241
241
private readonly _opts : SocketOptions ;
242
242
243
243
private ids : number = 0 ;
244
- private acks : object = { } ;
244
+ /**
245
+ * A map containing acknowledgement handlers.
246
+ *
247
+ * The `withError` attribute is used to differentiate handlers that accept an error as first argument:
248
+ *
249
+ * - `socket.emit("test", (err, value) => { ... })` with `ackTimeout` option
250
+ * - `socket.timeout(5000).emit("test", (err, value) => { ... })`
251
+ * - `const value = await socket.emitWithAck("test")`
252
+ *
253
+ * From those that don't:
254
+ *
255
+ * - `socket.emit("test", (value) => { ... });`
256
+ *
257
+ * In the first case, the handlers will be called with an error when:
258
+ *
259
+ * - the timeout is reached
260
+ * - the socket gets disconnected
261
+ *
262
+ * In the second case, the handlers will be simply discarded upon disconnection, since the client will never receive
263
+ * an acknowledgement from the server.
264
+ *
265
+ * @private
266
+ */
267
+ private acks : Record <
268
+ string ,
269
+ ( ( ...args : any [ ] ) => void ) & { withError ?: boolean }
270
+ > = { } ;
245
271
private flags : Flags = { } ;
246
272
private subs ?: Array < VoidFunction > ;
247
273
private _anyListeners : Array < ( ...args : any [ ] ) => void > ;
@@ -409,7 +435,7 @@ export class Socket<
409
435
const id = this . ids ++ ;
410
436
debug ( "emitting packet with ack id %d" , id ) ;
411
437
412
- const ack = args . pop ( ) as Function ;
438
+ const ack = args . pop ( ) as ( ... args : any [ ] ) => void ;
413
439
this . _registerAckCallback ( id , ack ) ;
414
440
packet . id = id ;
415
441
}
@@ -438,7 +464,7 @@ export class Socket<
438
464
/**
439
465
* @private
440
466
*/
441
- private _registerAckCallback ( id : number , ack : Function ) {
467
+ private _registerAckCallback ( id : number , ack : ( ... args : any [ ] ) => void ) {
442
468
const timeout = this . flags . timeout ?? this . _opts . ackTimeout ;
443
469
if ( timeout === undefined ) {
444
470
this . acks [ id ] = ack ;
@@ -458,11 +484,14 @@ export class Socket<
458
484
ack . call ( this , new Error ( "operation has timed out" ) ) ;
459
485
} , timeout ) ;
460
486
461
- this . acks [ id ] = ( ...args ) => {
487
+ const fn = ( ...args : any [ ] ) => {
462
488
// @ts -ignore
463
489
this . io . clearTimeoutFn ( timer ) ;
464
- ack . apply ( this , [ null , ... args ] ) ;
490
+ ack . apply ( this , args ) ;
465
491
} ;
492
+ fn . withError = true ;
493
+
494
+ this . acks [ id ] = fn ;
466
495
}
467
496
468
497
/**
@@ -485,17 +514,12 @@ export class Socket<
485
514
ev : Ev ,
486
515
...args : AllButLast < EventParams < EmitEvents , Ev > >
487
516
) : Promise < FirstArg < Last < EventParams < EmitEvents , Ev > > > > {
488
- // the timeout flag is optional
489
- const withErr =
490
- this . flags . timeout !== undefined || this . _opts . ackTimeout !== undefined ;
491
517
return new Promise ( ( resolve , reject ) => {
492
- args . push ( ( arg1 , arg2 ) => {
493
- if ( withErr ) {
494
- return arg1 ? reject ( arg1 ) : resolve ( arg2 ) ;
495
- } else {
496
- return resolve ( arg1 ) ;
497
- }
498
- } ) ;
518
+ const fn = ( arg1 , arg2 ) => {
519
+ return arg1 ? reject ( arg1 ) : resolve ( arg2 ) ;
520
+ } ;
521
+ fn . withError = true ;
522
+ args . push ( fn ) ;
499
523
this . emit ( ev , ...( args as any [ ] as EventParams < EmitEvents , Ev > ) ) ;
500
524
} ) ;
501
525
}
@@ -647,6 +671,30 @@ export class Socket<
647
671
this . connected = false ;
648
672
delete this . id ;
649
673
this . emitReserved ( "disconnect" , reason , description ) ;
674
+ this . _clearAcks ( ) ;
675
+ }
676
+
677
+ /**
678
+ * Clears the acknowledgement handlers upon disconnection, since the client will never receive an acknowledgement from
679
+ * the server.
680
+ *
681
+ * @private
682
+ */
683
+ private _clearAcks ( ) {
684
+ Object . keys ( this . acks ) . forEach ( ( id ) => {
685
+ const isBuffered = this . sendBuffer . some (
686
+ ( packet ) => String ( packet . id ) === id
687
+ ) ;
688
+ if ( ! isBuffered ) {
689
+ // note: handlers that do not accept an error as first argument are ignored here
690
+ const ack = this . acks [ id ] ;
691
+ delete this . acks [ id ] ;
692
+
693
+ if ( ack . withError ) {
694
+ ack . call ( this , new Error ( "socket has been disconnected" ) ) ;
695
+ }
696
+ }
697
+ } ) ;
650
698
}
651
699
652
700
/**
@@ -756,20 +804,25 @@ export class Socket<
756
804
}
757
805
758
806
/**
759
- * Called upon a server acknowlegement .
807
+ * Called upon a server acknowledgement .
760
808
*
761
809
* @param packet
762
810
* @private
763
811
*/
764
812
private onack ( packet : Packet ) : void {
765
813
const ack = this . acks [ packet . id ] ;
766
- if ( "function" === typeof ack ) {
767
- debug ( "calling ack %s with %j" , packet . id , packet . data ) ;
768
- ack . apply ( this , packet . data ) ;
769
- delete this . acks [ packet . id ] ;
770
- } else {
814
+ if ( typeof ack !== "function" ) {
771
815
debug ( "bad ack %s" , packet . id ) ;
816
+ return ;
817
+ }
818
+ delete this . acks [ packet . id ] ;
819
+ debug ( "calling ack %s with %j" , packet . id , packet . data ) ;
820
+ // @ts -ignore FIXME ack is incorrectly inferred as 'never'
821
+ if ( ack . withError ) {
822
+ packet . data . unshift ( null ) ;
772
823
}
824
+ // @ts -ignore
825
+ ack . apply ( this , packet . data ) ;
773
826
}
774
827
775
828
/**
0 commit comments