【问题标题】:Handling promise rejections outside the promise chain处理 Promise 链外的 Promise 拒绝
【发布时间】:2026-01-27 19:40:01
【问题描述】:

我熟悉 Promise 的工作原理以及未处理的 Promise 拒绝是什么,但是我有一个案例,我很难确定如何准确捕获这个特定的未处理的 Promise 拒绝错误。

我的目标是创建一个速率受限的重试处理程序,该处理程序可以与一系列顺序承诺函数内联。

我正在使用实现转换流的限制器类。进入流的消息仅以速率限制速率发出,用于强制重试速率。

我的重试处理程序实现了一个函数sendMessage,它返回一个带有结果的promise。如果消息发送失败,重试处理程序应重试发送消息,直到指定的最大重试次数。它还应该将传出消息限制为指定的速率。

重试处理程序本身并不实际发出 API 请求,这是由注册的 API 处理程序函数(它是抽象实际 API 调用的第 3 方库)完成的。 API 可能会以以下两种方式之一失败:

  1. 调用直接失败,来自处理程序的承诺被拒绝并被sendMessage函数的.catch捕获

  1. API 处理程序没有失败,但从处理程序返回的结果为 null,在这种情况下,errorEmitter 模块稍后会发出一个事件(errorEmitter 模块扩展 EventEmitter)。
class RetryMessageHandler extends Readable {

  constructor(msg, limit, interval, max, handler, errorEmitter) {
    super({objectMode: true});
    this._limiter = new RateLimiter(limit, interval);
    this._retryCount = 0;
    this._maxRetries = max;
    this._msg = msg;
    this._handler = handler;
    this._errorEmitter = errorEmitter;

    // The retry handler is intended as single use. The promise is
    // created and stored to deal with the 2 different rejection 
    // scenarios

    this._promise = new Promise((resolve, reject) => { 
      this._resolve = resolve; 
      this._reject = reject;
    });

    this.retryTrigger = this.retryTrigger.bind(this);
    this.sendMessage = this.sendMessage.bind(this);

    // catch the messages as they exit the rate limiter
    this._limiter.on('data', this.sendOrder);

    // add the listener for the message failed event
    this._errorEmitter.prependOnceListener('Sending Message Failed', this.retryTrigger);

    // allows send() to push messages into the rate limiter
    this.pipe(this._limiter);
  }

  // after instantiation of the retry handler this method is
  // called to send the message with retries
  sendMessage() {
    this._retryCount++;

    // attempt to send message via API message handler
    this._handler(this._msg)

      .then((result) => {

        // check if the result received was null
        if (result) {
          
          // remove the errorEmitter module listener
          this._errorEmitter.removeListener('Sending Message Failed', this.retryTrigger);

          // resolve the retry handler promise and return the 
          // result to the retry handler caller.
          this._resolve(result);
        }
      })
      .catch((err) => {
        
        // scenario 1: Message sending failed directly
        // Need to remove the errorEmitter to avoid triggering twice
        this._errorEmitter.removeListener('Sending Message Failed', this.retryTrigger);

        // Trigger the retry method
        this.send();
      })

    return this._promise;
  }

  // required function due to extending Readable
  _read(size: number) {
    /* no op */
  }

  // Scenario 2: Message sending failed indirectly.
  // This method that is called whenever the errorEmitter module
  // emits a 'Sending Message Failed' event
  retryTrigger(err) {
    // Trigger the retry method
    this.send();
  }

  // Handles the retry sending the message
  send() {

    // Check if we've already exceed the maximum number of retries
    if (this._retryCount >= this._maxRetries) {
      this._errorEmitter.removeListener('Sending Message Failed', this.retryTrigger);


      // THIS IS WHERE THE PROBLEM OCCURS
      // We need to throw an error because we've exceeded the max
      // number of retries. This error causes the unhandled promise rejection error
      throw new ExceededRetryCountError('Exceeded maximum number of retries', this._msg);
    }

    // if can retry we need to reset the errorEmitter listener
    this._errorEmitter.prependOnceListener('Sending Message Failed', this.retryTrigger);

    // Finally push the message into the rate limiter.
    // The message will come out the other side and call the
    // sendMessage() method and the whole thing starts over again
    this.push(this._msg);
  } 
}

最初,我使用 this._reject(new ExceededRetryCountError('Exceeded maximum number of retries', this._msg)); 而不是抛出错误,但这仍然有同样的问题。

我发现了这个相关的问题 (How can you retry after an exception in Javascript when using promises?),但这仅涉及在承诺链内部发生故障时的重试情况。

【问题讨论】:

  • sendMessage 返回this._promise 所以我假设调用者在事件队列中的某处而不是在同一个调用堆栈中捕获了拒绝,也许你可以在this._promise = new Promise... 之后尝试this._promise.catch(ignore=>ignore)
  • 谢谢,调用此 sendMessage 函数的代码在此函数后确实有一个 .catch,即 sendMessage().then().catch(err)

标签: javascript node.js stream dom-events es6-promise


【解决方案1】:

我想我可能已经找到导致未处理异常的问题。当返回 promise 的函数抛出错误时,catch 语句将在同一滴答声中执行。如果错误在函数执行的同一滴答上引发,则必须已经分配了 catch 处理程序。这通常是通过调用function().then( … ).catch(err) 来完成的,但是对于promise,它通常也可以直接处理let promise = function(); promise.then( ... ).catch(err) 返回的promise。但是在第二种情况下,这将导致未处理的异常,因为在抛出错误时,catch 语句尚未分配给 promise。附加 catch 语句后,它实际上会正确捕获错误,但已触发未处理的拒绝警告。

【讨论】: