typescriptpromise

How should I write a javascript promise to wait for a response from socket stream?


I have an object that communicates two-way with another program ('remote') using TCP socket. The incoming messages (from remote) may arrive unsolicited as well as a response to a message sent to the remote. How should I write a method returning a Promise that would send message to the remote and resolve only after the response is received? This should work while the communication through the socket is already running, i.e. kind of "plug in" the function waiting for the one-time response?

// This is simplified excerpt of the original class 
class RigControl {

private socket : Socket = new Socket();
private host: string = "localhost" ;
private port: number = 4532;

connect( options? : { host?: string, port?: number }) : Promise<void> {
  this.host = options?.host ?? this.host;
  this.port = options?.port ?? this.port;
  // .. some unrelated setup skipped 
  this.socket.on( 'data', this.processMessage.bind(this) );
  return new Promise( (resolve, reject) => {
    this.socket.connect( this.port, this.host, 
      () => {
        resolve();
      }
    );
    this.socket.on( 'error', ( err ) => { reject(err) });
  });
}

processMessage ( data: Buffer | string ) {
  // do some unconditional processing, interpret the incoming data etc.
}

sendCommand( command: string ) {
  this.socket.write( command )
}

}

What I would like to write is a (async) method returning Promise<string> that would send a command and the resolve only after a response is received. I am not sure how this should be written. I had an idea it could be done this way:

class RigControl {

private socket : Socket = new Socket();
private host: string = "localhost" ;
private port: number = 4532;
private responsePromise : Promise<string> | undefined ;

// ... same methods as above except processMessage(data)

processMessage( data : Buffer | string ) {
  // regular processing of any unsolicited incoming message 
  if( this.responsePromise !== undefined ) {
    if( data.toString('ascii') == 'OK' )
      {this.responsePromise.resolve( data );} // e.g. report the response unchanged to the caller of sendCommandWithResponse()
    else {this.responsePromise.reject( data );}
    this.responsePromise = undefined ; // wouldn't this prevent resolve() or reject() from actually executing?
  }
}

sendCommandWithResponse( command: string ) : Promise<string> {

  this.responsePromise = new Promise<string>( ( resolve, reject ) => {
    this.sendCommand( command );
  });
  return this.responsePromise ;
}

}

Does this make sense? Can I write the executor without calling neither resolve() nor reject() and can I call them in entirely different place?


Solution

  • Yes. But you need to store the response and reject functions.

    e.g.

    interface PendingPromiseFunctions<T> {
      resolve: (value: T) => void;
      reject: (reason: any) => void;
    }
    
    class RigControl {
    
      private socket : Socket = new Socket();
      private host: string = "localhost" ;
      private port: number = 4532;
      private responsePromise: PendingPromiseFunctions<string> | undefined;
    
      // ... same methods as above except processMessage(data)
    
      processMessage( data : Buffer | string ) {
        // regular processing of any unsolicited incoming message 
        if( this.responsePromise !== undefined ) {
          if( data.toString('ascii') == 'OK' )
            {this.responsePromise.resolve( data );} // e.g. report the response unchanged to the caller of sendCommandWithResponse()
          else {this.responsePromise.reject( data );}
          this.responsePromise = undefined ; // wouldn't this prevent resolve() or reject() from actually executing?
        }
      }
    
      sendCommandWithResponse( command: string ) : Promise<string> {
        const pendingResponsePromise = new Promise<string>((resolve, reject) => {
          this.sendCommand( command );
          this.responsePromise = { resolve, reject };
        });
    
        return pendingResponsePromise;
      }
    
    }