-
Notifications
You must be signed in to change notification settings - Fork 0
Error conditions, retry, and Error.cause #11
Description
Within Workers we have been having a discussion about how to communicate to users via Errors that the conditions leading to an error are temporary and that the user should retry their operation. The how and when to retry is not important here.
For example, a fetch() promise can fail for many reasons. The network path could temporarily be down, the URL could be blocked, the header could be malformated, etc. We want to be able to clearly indicate that the user can/should retry their operation without requiring that the user resort to parsing the error message.
We have several possible paths forward, all of which have the same fundamental problem. We'd like to get consensus on which approach folks would find the most agreeable.
Option 1: New error types
const err = new Error('an error occurred');
Object.defineProperty(err, 'name', { value: 'RetriableError' });Option 2: Non-standard own properties on Error
const err = new Error('an error occurred');
err.retriable = true;Option 3: Using cause
const err = new Error('an error occured', { cause: { retriable: true } })Option 4: Using AggregateError
// The first object is always an error but the additional things communicate
// the additional structured information we want.
const err = new AggregateError([
new Error('an error occurred'),
{ retriable: true }
])Option 5: ??
Other ideas?
Current Thinking
My current thinking here is to prefer Option 3, using the cause property.
Specifically, pulling out to a logical level: The purpose of the cause is to communicate the reason for this error. That reason might be that another Error was thrown, or it might be that some other condition occurred. For instance, the network was down, or there was an internal error, etc. So let's differentiate between Error and Condition.
If I have a transient condition and want to communicate that the user should retry their operation, then I could logically do something like:
cont condition = {
// The condition is temporary....
transient: true,
// The operation is retriable...
retriable: true,
};
const err = new Error('oops that failed', { cause: condition });The challenge with this, of course, is interoperability. If workers chooses to use cause in this way but other fetch() implementations choose to use cause in other ways then we can run into interop issues. To be clear, ALL of the options suffer from this exact problem.
Proposal
The proposal I would like to make is to define a new ErrorCondition interface specifically for use with cause
Essentially (treat this as a discussion example to express intent... the actual proposal can be refined):
dictionary ErrorConditionInit {
boolean transient = false;
boolean retriable = false;
DOMString name = "";
};
interface ErrorCondition {
constructor(optional DOMString message = "", optional ConditionInit init = {});
readonly attribute boolean transient;
readonly attribute boolean retriable;
readonly attribute DOMString name;
readonly attribute DOMString message;
}
Note that this interface intentionally mimics DOMException with the inclusion of a name and message accessors.
Example use (assuming the proposal to add cause to DOMException goes through):
const err = new DOMException('The operation failed', {
name: 'NETWORK_ERR',
cause: new ErrorCondition('The network path is down', {
transient: true,
retriable: true,
})
});
console.log(err.cause.transient); // true
console.log(err.cause.retriable); // true
To be clear, I don't really have strong opinions on exactly how we solve this use case. My only requirement is that we have a mechanism for reliably communicating transient/retriable conditions that is interoperable across runtimes.
Some questions
- How are retriable errors like this handled elsewhere on the web?