Imagine if there existed a method to accomplish that.
Picture a fantastical machine capable of always determining whether .then
is invoked with a function as the second argument for a specific promise.
Why does it matter?
This means you can identify if someone called:
myPromise.then(..., function(){ F(); });
At any given moment from any location. And have G()
act as the default outcome.
And then what?
You could take an entire program consisting of plenty of code P (1) and transform that code into:
var myPromise = $q.reject();
P; // incorporate the program's code
myPromise.then(null, function(){}); // add a handler
Okay, so I can achieve that, but why?
Now our miraculous machine can analyze an arbitrary program P and determine if myPromise
had a rejection handler appended to it. This occurs only if and when P
does not include an infinite loop (i.e., it stops). Consequently, detecting the addition of a catch handler is equivalent to solving the halting problem, which is impossible. (2)
In essence - it is unfeasible to determine whether a .catch
handler is ever attached to a promise.
No more complicated explanations, give me a solution!
Excellent reaction! While this problem is theoretically unsolvable, it is quite manageable in practical scenarios. The key lies in a rule of thumb:
If an error handler isn't added within a microtask (digest in Angular) – no error handlers will be attached and the default handler can be executed instead.
In short: You don't usually attach .then(null, function(){})
asynchronously. Although promises are resolved asynchronously, handlers are typically attached synchronously, making this technique effective.
// maintaining library independence as much as possible.
var p = myPromiseSource(); // obtain a promise from the source
var then = p.then; // starting 1.3+, you can grab the constructor and use prototype instead
var t = setTimeout(function(){ // in angular utilize $timeout, not a microtask but acceptable
defaultActionCall(p);// execute the default action!
});
// .catch delegates to `.then` in almost every library, hence simply `then`
p.then = function then(onFulfilled, onRejected){
// delegate, disregarding progression since it is rarely used anyway.
if(typeof onRejected === "function"){ // eliminate default action
clearTimeout(t); // `timeout.cancel(t)` in Angular
}
return then.call(this, onFulfilled, onRejected);
};
Is that all?
I just want to emphasize that situations necessitating such an extreme approach are uncommon. When considering implementing rejection tracking in io – numerous individuals proposed that if a promise is rejected without a catch
, the entire application should probably cease operation. So proceed with caution :)
(1) assume P does not contain a variable myPromise, if it does rename myPromise to something P does not contain.
(2) Admittedly - one might argue that inspecting the code of P without executing it can reveal whether myPromise
receives a rejection handler. Formally, we alter every return
in P and other termination forms to a return myPromise.then(null, function(){})
rather than placing it at the end. By doing this, we capture the "conditional aspect."