【问题标题】:Proper way of creating a EventEmitter that works with Promises in the background创建一个在后台使用 Promise 的 EventEmitter 的正确方法
【发布时间】:2018-07-25 03:30:04
【问题描述】:

我正在创建一个“类”,它发出诸如errordatadownloadFileinitialize 之类的事件。每个事件在发出请求后触发,每个事件由同名的方法触发:

class MyClass extends EventEmitter {
  constructor(data) {
    this.data = data

    this.initialize()
      .then(this.downloadFile)
      .then(this.data)
      .catch(this.error)
  }

  initialize() {
    const req = superagent.post('url...')
    superagent.send(data)
    const res = await req // this will actually fire the request
    this.emit('initialize')
    this.url = res.body
    return res
  }

  downloadFile() {
    const req = superagent.put(this.url)
    const res = await req; // this will actually fire the request
    req.on('progress', (progress) => this.emit('downloadFile', progress)
    //
    // save to disk
    //
    return res
  }

  data() {
    // Next in the sequence. And will fire the 'data' event: this.emit('data', data)
  }

  error(err) {
    this.emit('error', err)
  }
}

之后我就有了要调用的数据方法。我的疑问是:是否有一种设计模式可以在不使用 Promises 的情况下按顺序调用事件?目前我正在使用链接,但我觉得这不是最好的方法,也许我错了。

this.initialize()
  .then(this.downloadFile)
  .then(this.data)
  .catch(this.error)

但我觉得这可能是一个更好的方法。


bergi 问题的答案:

a) 你为什么使用类语法?

因为从 EventEmitter 继承更容易,而且我个人认为它比使用构造函数更具可读性 函数,例如:

function Transformation(data) {
  this.data = data
}

// Prototype stuffs here

b) 如何使用此代码

我正在创建一个客户端来与我的 API 交互。想法是用户可以看到后台发生的事情。例如:

const data = {
 data: {},
 format: 'xls',
 saveTo: 'path/to/save/xls/file.xls'
}

const transformation = new Transformation(data)

// Events
transformation.on('initialize', () => {
  // Here the user knows that the transformation already started
})

transformation.on('fileDownloaded', () => {
  // Here the file has been downloaded to disk
})

transformation.on('data', (data) => {
  // Here the user can see details of the transformation -
  //   name,
  //   id,
  //   size,
  //   the original object,
  //   etc
})

transformation.on('error', () => {
  // Here is self explanatory, if something bad happens, this event will be fired
})

c) 它应该做什么?

用户将能够将带有数据的对象转换为 Excel。

【问题讨论】:

  • definitely should not put the promise chain in the constructor(完全不清楚为什么这应该是class
  • 不,promise 是按顺序运行的 解决方案(你当然可以用 async/await 替换你的链接,反正你已经在使用它了)。事件比较麻烦,你为什么要这么做?
  • 您发送了一个答案,说从构造函数返回 Promise 是一件坏事,但我不是 @Bergi
  • 没错,你不会退回它,但那更糟:-D。我的答案是链接线程解释了为什么在构造函数中启动异步操作通常是一件坏事。
  • 是的,我看到了,我将更改代码以避免这种情况。 @Bergi,你不觉得看到一个使用 Promises 但发出事件的实现很奇怪吗?

标签: javascript node.js class promise eventemitter


【解决方案1】:

听起来您正在创建的transformation 对象仅被调用者用于监听事件。用户不需要具有要获取的属性或要调用的方法的class 实例。所以不要做一个。 KISS(保持超级简单)。

function transform(data) {
  const out = new EventEmitter();
  async function run() {
    try {
      const url = await initialise();
      const data = await downloadFile(url);
      out.emit('data', data);
    } catch(err) {
      out.emit('error', err);
    }
  }

  async function initialise() {
    const req = superagent.post('url...')
    superagent.send(data)
    const res = await req // this will actually fire the request
    out.emit('initialize')
    return res.body
  }

  async function downloadFile(url) {
    const req = superagent.put(url)
    req.on('progress', (progress) => out.emit('downloadFile', progress)
    const res = await req; // this will actually fire the request
    //
    // save to disk
    //
    return data;
  }

  run();
  return out;
}

省略(仅一次?)dataerror 事件并仅返回一个 Promise 以及事件发射器用于进度通知可能会更简单:

  return {
    promise: run(), // basically just `initialise().then(downloadFile)`
    events: out
  };

【讨论】:

  • 天才!我不知道我不需要一个类来使用 EventEmitter。谢谢伯吉!
【解决方案2】:

如果您想要另一种方式来按顺序调用事件,并且如果您使用的是支持 ES7 的 Node.js 版本,则可以执行以下操作:

class MyClass extends EventEmitter {
  constructor(data) {
    this.data = data;

    this.launcher();
  }

  async launcher() {
    try {
      await this.initialize();
      await this.downloadFile();
      await this.data();
    }
    catch(err) {
      this.error(err);
    }
  }

  initialize() {
    const req = superagent.post('url...');
    superagent.send(data);
    this.emit('initialize');
    this.url = req.body;
    return req;
  }

  downloadFile() {
    const req = superagent.put(this.url);
    req.on('progress', (progress) => this.emit('downloadFile', progress)
    //
    // save to disk
    //
    return req;
  }

  data() {
    // Next in the sequence. And will fire the 'data' event: this.emit('data', data)
  }

  error(err) {
    this.emit('error', err)
  }
}

说明:在你的函数中,你的 Promises 不是 awaiting,而是在根级别返回 Promises 和 await

【讨论】:

  • 我认为initialize 会得到错误 res is not defined。
  • 是的,已编辑,我在重写代码时忘记了这一点。顺便还有一个问题需要处理:在请求响应时加载响应体,否则它可能保持为空
猜你喜欢
  • 1970-01-01
  • 2015-09-01
  • 2013-01-17
  • 1970-01-01
  • 2021-07-03
  • 1970-01-01
  • 2011-04-24
  • 1970-01-01
  • 2019-10-21
相关资源
最近更新 更多