【问题标题】:Resolve Promise when event fires without duplicating event listeners在不复制事件监听器的情况下触发事件时解决 Promise
【发布时间】:2018-03-30 22:58:40
【问题描述】:

我正在尝试在 EventEmitter 事件被触发时解决一个 Promise。

包含函数接受一个参数并将其添加到堆栈中。然后将项目从堆栈中取出以进行任意处理,并触发 success 或 error 事件。

public async processData(payload: Payload) {
  // Push data onto a stack
  this._myStack.push(payload);

  return new Promise((resolve, reject) => {
    myEventEmitter.on('processSuccess', data => {
      resolve(data);
    });

    myEventEmitter.on('processError', error => {
      reject(error);
    });
  });
}

问题是每次将项目添加到堆栈时都会添加一个新侦听器。这意味着当processSuccess 或processError 事件被触发时,承诺将为堆栈上的每个项目返回。

示例:如果堆栈上有 11 个项目,并且发出 processSuccess,则 data 将返回 11 次。

当其中一个事件被发出而不返回堆栈上的每个项目时,有没有办法解决承诺?也就是说,如何在不为每个事件创建一堆事件监听器的情况下做到这一点?

【问题讨论】:

  • 什么逻辑确保处理堆栈项的完成按照它们被推入堆栈的顺序发生?也就是说,你怎么知道一个事件是针对什么有效载荷发出的?
  • 从栈顶获取数据,处理,完成事件,然后弹出。 peek()process()emit()pop()。返回的有效负载本身中有识别信息。 myEventEmitter.emit('processSuccess', data);
  • 在将有效负载推送到堆栈后,代码似乎等待处理成功或失败 - 多个有效负载如何同时进入堆栈或者我错过了什么?
  • 如果我用 5 个不同的参数调用这个方法 5 次,那么它会将所有 5 个参数添加到堆栈中。当用户选择处理它时,堆栈上的项目的实际处理发生(为简单起见从示例中删除)。此时,processSuccess 或 processError 会被发出,并且已处理的项目(用户选择对其执行操作的项目)被移除,并且用户可以选择对堆栈中的下一个项目执行操作。
  • 知道了。 return await new Promise(async ....) 让我感到困惑 - 它不需要在执行程序中使用 await 运算符或 async 声明。

标签: javascript node.js typescript promise eventemitter


【解决方案1】:

假设您可以使用随事件发送的来自data 本身的有效负载识别信息(成功和错误),那么您可以这样做:

public processData(payload: Payload) {
    // Push data onto a stack
    this._myStack.push(payload);

    return new Promise((resolve, reject) => {

      function resolver(data) {
        // if this event belongs to this payload
        if (data.payload === payload) {
          resolve(data);
          clearHandlers();
        }
      }

      function rejecter(error) {
        // if this event belongs to this payload
        if (error.payload === payload) {
          reject(error);
          clearHandlers();
        }
      }

      // clear event handlers
      function clearHandlers() {
        myEventEmitter.removeListener('processSuccess', resolver);
        myEventEmitter.removeListener('processError', rejecter);
      }

      myEventEmitter.on('processSuccess', resolver);  
      myEventEmitter.on('processError', rejecter);
    });
}

如果您无法确定哪个有效负载对应于哪个事件,那么使用此通用结构的唯一方法是永远不要让相同的myEventEmitter 一次用于多个processData() 操作。这是因为您需要确保当processSuccess 或processError 事件发生时,您确切知道它属于哪个有效负载。您必须知道,以避免同时运行中的单独异步操作之间的交叉耦合。这可以通过四种方式实现:

  1. 负载是每个事件的一部分,因此您可以比较它是否是正确的负载(就像我的第一个代码示例一样)。
  2. myEventEmitter 对象永远不会在同时运行的操作之间共享。
  3. 没有两个使用共享 myEventEmitter 的异步操作会同时运行。
  4. 您以某种方式想出唯一的消息名称,以便为每个正在运行的单独有效负载侦听和触发。这也需要与事件触发方合作。

对于上面的选项 #2 或 #3,您可以使用以下代码:

public processData(payload: Payload) {
    // Push data onto a stack
    this._myStack.push(payload);

    return new Promise((resolve, reject) => {
      function resolver(data) {
        resolve(data);
        clearHandlers();
      }
      function rejecter(err) {
        reject(error);
        clearHandlers();
      }
      // clear event handlers
      function clearHandlers() {
        myEventEmitter.removeListener('processSuccess', resolver);
        myEventEmitter.removeListener('processError', rejecter);
      }

      myEventEmitter.on('processSuccess', resolver);  
      myEventEmitter.on('processError', rejecter);
    });
}

【讨论】:

    【解决方案2】:

    使用事件命名空间。参考https://css-tricks.com/namespaced-events-jquery/

    为事件创建某种动态命名空间,并检查该命名空间以匹配公共事件侦听器。当任何命名空间匹配时解决 promise。

    【讨论】:

    • 我不相信节点 EventEmitter 支持命名空间。
    【解决方案3】:

    所以基本上为了一个好的答案,我需要更多的信息,但总的来说,你可以做到以下几点:

    • 有效负载应该有一个唯一的 ID
    • 数据响应应包含相同的 id
    • 错误响应应该包含相同的 id

    因此,基于此,您的有效负载对象应该是这样的:

    {"id": ..., ....}
    

    响应数据对象

    {"id": ..., ....}
    

    错误数据对象

    {"id": ..., ....}
    

    您可以轻松地执行以下操作:

    class MyClass {
        private _myStack: Array<Payload>;
        private _promisesFunctions: Array<any>;
    
        constructor() {
            myEventEmitter.on('processSuccess', data => {
                this._promisesFunctions[data.id].resolve(data);
            });
    
            myEventEmitter.on('processError', error => {
                this._promisesFunctions[error.id].reject(error);
            });
        }
    
        public async processData(payload: Payload) {
            // Push data onto a stack
            this._myStack.push(payload);
    
            return await new Promise(async (resolve, reject) => {
                this._promisesFunctions[payload.id] = { resolve, reject };
            });
        }
     }

    如果您没有这种可能性或者您无法控制这 3 个对象,请提供所有这 3 个对象的示例或至少提供有关此问题的更多信息,我会更新我的回复

    【讨论】:

    • 抱歉信息有限。尽量简化。我确实控制了所有三个对象,所以这看起来是一个很好的解决方案。我会尽快测试一下
    猜你喜欢
    • 2022-01-14
    • 2023-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-22
    • 2017-01-08
    • 1970-01-01
    相关资源
    最近更新 更多