Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EventEmitter in the middle of a chain of Promises

I am doing something that involves running a sequence of child_process.spawn() in order (to do some setup, then run the actual meaty command that the caller is interested in, then do some cleanup).

Something like:

doAllTheThings()
  .then(function(exitStatus){
    // all the things were done
    // and we've returned the exitStatus of
    // a command in the middle of a chain
  });

Where doAllTheThings() is something like:

function doAllTheThings() {
  runSetupCommand()
    .then(function(){
      return runInterestingCommand();
    })
    .then(function(exitStatus){
      return runTearDownCommand(exitStatus); // pass exitStatus along to return to caller
    });
}

Internally I'm using child_process.spawn(), which returns an EventEmitter and I'm effectively returning the result of the close event from runInterestingCommand() back to the caller.

Now I need to also send data events from stdout and stderr to the caller, which are also from EventEmitters. Is there a way to make this work with (Bluebird) Promises, or are they just getting in the way of EventEmitters that emit more than one event?

Ideally I'd like to be able to write:

doAllTheThings()
  .on('stdout', function(data){
    // process a chunk of received stdout data
  })
  .on('stderr', function(data){
    // process a chunk of received stderr data
  })
  .then(function(exitStatus){
    // all the things were done
    // and we've returned the exitStatus of
    // a command in the middle of a chain
  });

The only way I can think to make my program work is to rewrite it to remove the promise chain and just use a raw EventEmitter inside something that wraps the setup/teardown, something like:

withTemporaryState(function(done){
  var cmd = runInterestingCommand();
  cmd.on('stdout', function(data){
    // process a chunk of received stdout data
  });
  cmd.on('stderr', function(data){
    // process a chunk of received stderr data
  });
  cmd.on('close', function(exitStatus){
    // process the exitStatus
    done();
  });
});

But then since EventEmitters are so common throughout Node.js, I can't help but think I should be able to make them work in Promise chains. Any clues?

Actually, one of the reasons I want to keep using Bluebird, is because I want to use the Cancellation features to allow the running command to be cancelled from the outside.

like image 561
d11wtq Avatar asked Aug 09 '14 07:08

d11wtq


People also ask

When should I use EventEmitter?

Use event emitter if you need to notify the user of a state change. For testing purpose, if you want to make sure a function is called inside a function, emit an event.

Which is the commonly used events in EventEmitter?

When an EventEmitter instance faces any error, it emits an 'error' event. When a new listener is added, 'newListener' event is fired and when a listener is removed, 'removeListener' event is fired. EventEmitter provides multiple properties like on and emit.

What is true about EventEmitter on property?

on property is used to bind a function with the event. Q 25 - Which of the following is true about EventEmitter. emit property? A - emit property is used to locate an event handler.

Which of the following is an EventEmitter?

Which of the following is an event emitter? Explanation: The process object is an event emitter.


1 Answers

There are two approaches, one provides the syntax you originally asked for, the other takes delegates.

function doAllTheThings(){
     var com = runInterestingCommand();
     var p = new Promise(function(resolve, reject){
         com.on("close", resolve);
         com.on("error", reject);
     });
     p.on = function(){ com.on.apply(com, arguments); return p; };
     return p;
}

Which would let you use your desired syntax:

doAllTheThings()
  .on('stdout', function(data){
    // process a chunk of received stdout data
  })
  .on('stderr', function(data){
    // process a chunk of received stderr data
  })
  .then(function(exitStatus){
    // all the things were done
    // and we've returned the exitStatus of
    // a command in the middle of a chain
  });

However, IMO this is somewhat misleading and it might be desirable to pass the delegates in:

function doAllTheThings(onData, onErr){
     var com = runInterestingCommand();
     var p = new Promise(function(resolve, reject){
         com.on("close", resolve);
         com.on("error", reject);
     });
     com.on("stdout", onData).on("strerr", onErr);
     return p;
}

Which would let you do:

doAllTheThings(function(data){
    // process a chunk of received stdout data
  }, function(data){
    // process a chunk of received stderr data
  })
  .then(function(exitStatus){
    // all the things were done
    // and we've returned the exitStatus of
    // a command in the middle of a chain
  });
like image 186
Benjamin Gruenbaum Avatar answered Sep 26 '22 08:09

Benjamin Gruenbaum