Manuel Spigolon
Manuel Spigolon

Reputation: 12880

Combine Promise and EventEmitter

I have create this "simple pattern" that works for combine Promise and EventEmitter (with nodejs).

But: I'm wondering if there is a better way score a goal?

const { EventEmitter } = require('events');
const fs = require('fs');

function doSomething(parameters) {
  const emitter = new EventEmitter();

  const promise = new Promise((resolve, reject) => {
    // DO DIRTY JOB
    fs.readdir(parameters.directory, (err, files) => {
      if (err) {
        reject(err);
        return;
      }
      files.forEach(file => emitter.emit('update-event', file));
      resolve(`I'm done: ${parameters.param} world`);
    });
  });
  return { promise, emitter };
}

const work = doSomething({ param: 'hello', directory: './' });
work.emitter.on('update-event', data => console.log(`Update ${data}`));
work.promise.then(console.log).catch(console.error);

I was thinking like:

doSomething(...).on(...).then(...)

but I can't figure out how do that.

Upvotes: 9

Views: 14701

Answers (4)

hellopeach
hellopeach

Reputation: 1074

Personally I'm not sure how the accepted answer is related to the OP's question, anyway I think I do have found a rather simple (but maybe not very nice) way of accomplishing the specific doSomething(...).on(...).then(...) thing asked by the OP. Taking the OP's example code, we can just do something like the following:

const { EventEmitter } = require('events');
const fs = require('fs');

function doSomething(parameters) {
  var resolves;
  var rejects;
  const emitter = new EventEmitter();
  const promise = new Promise((resolve, reject) => {
    resolves = resolve;
    rejects = reject;
  });
  promise.on = emitter.on;
  promise.emit = emitter.emit;

  // DO DIRTY JOB
  fs.readdir(parameters.directory, (err, files) => {
    if (err) {
      rejects(err);
      return;
    }
    files.forEach(file => promise.emit('update-event', file));
    resolves(`I'm done: ${parameters.param} world`);
  });

  return promise;
}

const work = doSomething({ param: 'hello', directory: './' });
work.on('update-event', data => console.log(`Update ${data}`))
  .then(console.log)
  .catch(console.error);

It works for my limited cases so far, and both the event and the promise can be chained without issues as far as I know. There might be problems for more complicated use-cases that I have not encountered yet, but it does serve the purpose of chaining doSomething(...).on(...).then(...) like the OP asked.

Upvotes: 2

Manuel Spigolon
Manuel Spigolon

Reputation: 12880

Node.js has built a function for this: the require('events').once function! Here the PR.

It has been released with Node [v11.13] (https://nodejs.org/en/blog/release/v11.13.0/)

An example usage (from docs):

const { once, EventEmitter } = require('events');
async function run() {
  const ee = new EventEmitter();
  process.nextTick(() => {
    ee.emit('myevent', 42);
  });
  const [value] = await once(ee, 'myevent');
  console.log(value); // 42

  const err = new Error('kaboom');
  process.nextTick(() => {
    ee.emit('error', err);
  });

  try {
    await once(ee, 'myevent');
  } catch (err) {
    console.log('error happened', err);
  }
}

run();

Upvotes: 18

Matthieu Fron
Matthieu Fron

Reputation: 1

I would suggest this :

import EventPromised from "event-promised";

function doSomething(parameters) {
    return new EventPromised((resolve, reject, emit) => {
        fs.readdir(parameters.directory, (err, files) => {
            if (err) {
                reject(err);
                return;
            }
            files.forEach(file => emit('update-event', file));
            resolve(`I'm done: ${parameters.param} world`);
        });
    });
}
doSomething({ param: 'hello', directory: './' })
    .on('update-event', data => console.log(`Update ${data}`))
    .then(console.log)
    .catch(console.error);

Upvotes: -3

Bergi
Bergi

Reputation: 664538

No, you shouldn't combine an event emitter and a promise in one object. Returning them separately, just like you did, is fine.

Of course, in your particular example, there's not really a reason to use an event emitter at all. It just fires when the promise fulfills anyway. Much simpler:

const fs = require('fs');
function doSomething(parameters) {
  return new Promise((resolve, reject) => {
    // DO DIRTY JOB
    fs.readdir(parameters.directory, (err, files) => {
      if (err) reject(err);
      else resolve(Object.assign(files, parameters));
    });
  });
}

doSomething({ param: 'hello', directory: './' }).then(files => {
  for (const data of files) {
    console.log(`Update ${data}`)
  }
  return `I'm done: ${files.param} world`;
}).then(console.log, console.error);

Upvotes: 1

Related Questions