结合 Promise 和 EventEmitter

Combine Promise and EventEmitter

我创建了这个 "simple pattern",它适用于组合 PromiseEventEmitter(使用 nodejs)。

但是:我想知道是否有更好的进球方式?

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);

我在想:

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

但我不知道该怎么做。

不,您不应该在一个对象中组合事件发射器和承诺。像您一样单独退货,没问题。

当然,在您的特定示例中,根本没有理由使用事件发射器。它只是在承诺实现时触发。更简单:

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);

我建议这样做:

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);

Node.js 为此构建了一个函数:require('events').once 函数!这里是 PR.

它已与 Node [v11.13] 一起发布 (https://nodejs.org/en/blog/release/v11.13.0/)

用法示例(来自文档):

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();

我个人不确定接受的答案与 OP 的问题有何关系,无论如何我认为我确实找到了一种相当简单(但可能不是很好)的方法来完成特定的 doSomething(...).on(...).then(...) 事情OP问。使用 OP 的示例代码,我们可以执行以下操作:

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);

到目前为止,它适用于我有限的案例,据我所知,事件和承诺都可以毫无问题地链接起来。对于我还没有遇到过的更复杂的用例,可能存在问题,但它确实起到了像 OP 要求的那样链接 doSomething(...).on(...).then(...) 的目的。