我熟悉承诺如何运作以及未经处理的承诺拒绝是多少,但我有一个案例,我很难确定如何捕捉这个特定的未处理的承诺拒绝错误。
我的目标是创建一个速率受限的重试处理程序,它可以与一系列顺序的promise函数内联。
我正在使用实现转换流的限制器类。进入流的消息仅以速率限制速率发出,用于强制执行重试速率。
我的重试处理程序实现了一个函数sendMessage
,它返回一个带有结果的promise。如果消息未能发送重试处理程序,则应重试将消息发送到指定的最大重试次数。它还应将传出消息限制为指定的速率。
重试处理程序实际上并不实际发出API请求,这是由已注册的API处理函数(它是抽象实际API调用的第三方库)完成的。 API可以通过以下两种方式之一失败:1)调用直接失败,处理程序的promise被拒绝并被sendMessage函数的.catch捕获或2)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?),但这只涉及在promise链中发生故障时的重试情况。
答案 0 :(得分:0)
我想我可能发现导致未处理异常的问题。当一个返回一个promise的函数抛出一个错误时,catch语句会在同一个tick上执行。如果错误抛出与执行函数相同的刻度,则必须已经分配了catch处理程序。这通常是通过调用function().then( … ).catch(err)
完成的,但是使用promises通常也可以通过let promise = function(); promise.then( ... ).catch(err)
直接处理返回的promise。在第二种情况下,虽然这会导致未处理的异常,因为在抛出错误时,catch语句尚未分配给promise。附加catch语句后,它将实际正确捕获错误,但已触发未处理的拒绝警告。