$http Timeout Promise: Resolution vs. Rejection Behavior
0 reputation · 06 Jan 2024, 01:42 UTC
0 reputation · 06 Jan 2024, 01:42 UTC
AngularJS $http service supports a timeout configuration that accepts a promise. When the promise resolves, the pending HTTP request is aborted immediately. However, the framework’s response when the timeout promise is rejected remains ambiguous. Developers often use a $q.defer() promise as the timeout token, expecting that rejection would cancel the request, but the documented behavior does not explicitly state whether the request is aborted or allowed to complete to termination. This uncertainty affects cancellation strategies in component lifecycles and long-running applications where pending requests must be cleaned up upon scope destruction.
The core question is whether rejecting the timeout promise should be treated as a cancellation signal or simply ignored by the HTTP layer. Does rejecting the timeout promise cancel the $http request, or is the request permitted to finish normally? What is the intended design rationale for this behavior? What is the recommended pattern for reliably cancelling $http requests when a component or scope is destroyed?
31275 reputation · 06 Jan 2024, 10:24 UTC
Resolving the timeout promise cancels the request; rejecting it does nothing. In AngularJS (1.2 and later), when the promise passed via the timeout config resolves, the pending XHR is aborted and the error callback fires with status -1. When that same promise is rejected, the HTTP layer ignores the rejection entirely and the request runs to normal completion. Rejection is not a cancellation signal.
The implementation attaches only a fulfillment handler to the timeout token. Conceptually, the relevant code in src/ng/http.js reduces to:
if (timeout) {
$q.when(timeout).then(function () {
req.abort(); // only runs on RESOLUTION
}, angular.noop); // rejection swallowed by a no-op
}Because the rejection path is a deliberate angular.noop, a rejected token can never abort the request. The design treats the promise as a one-shot "cancel now" event: resolution is the signal, and rejection is reserved for the token's own internal error state, which the HTTP layer chooses not to act on. This is confirmed behavior in the source, not just inference, and it has been stable across the AngularJS 1.x line since promise-based timeouts were introduced. If you are on a version earlier than 1.2, verify against your specific build, since promise handling differed in older releases.
Use a $q.defer() as a cancellation token and resolve it when the owning scope is destroyed:
angular.module('app').controller('MyCtrl',
function ($scope, $http, $q) {
var canceler = $q.defer();
$http.get('/api/slow-endpoint', { timeout: canceler.promise })
.then(function (res) { /* success */ },
function (err) {
if (err.status === -1) { /* aborted */ }
});
$scope.$on('$destroy', function () {
canceler.resolve(); // NOT canceler.reject()
});
});If several conditions should be able to cancel the request, resolve the same deferrer from any of them — resolution is idempotent for this purpose. Avoid calling reject() on the token, and do not mix a numeric timeout with a promise in the same config; the option accepts one or the other.
$http.get at an endpoint with a server-side delay of ~10 seconds, passing { timeout: canceler.promise }.canceler.resolve(): the Network tab shows the request as cancelled and the error handler receives status -1.canceler.reject(): the request completes normally and the success handler fires after the full delay.$scope.$destroy() (e.g., via route change) with the resolve wired in, and confirm the pending request aborts.One caveat worth flagging: the exact internal wiring is version-specific, so if you maintain a pinned legacy build, grep your copy of angular.js for the timeout handling in the $http factory to confirm the rejection branch is still a no-op before relying on it.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.