Reputation: 8948
I'm using a deferred promise to abort a $http
request (like explained in this post).
However, I'm unsure of how I should propagate the abort()
function added at the root level. Everytime I call .then()
on the promise, a new promise (without the abort()
function) is returned.
See my example below. I have a controller calling a service, that in turn calls another service (where the $http
request is made). The example does not work, since promise
in MyController
is not the same as the one that was returned in restService
, and therefore does not have a function called abort()
.
abort()
function so that it is available in MyController
?then()
on that (like in the examples at the bottom)?. What happens with the then()
calls? Are they still invoked "synchronously"?app.controller("MyController", function($scope, dataService) {
var promise;
$scope.loadAndAbortData = function () {
promise = dataService.getData().then(updateUI);
};
$scope.abort = function () {
if (promise) {
promise.abort();
}
};
}
app.service("dataService", function(restService) {
var service = {
getData: function () {
return restService.get().then(function (response) {
var modifiedData = modifyData(response.data);
return modifiedData;
}, function (response) {
handleError(response.data);
$q.reject(response.data);
});
};
};
return service;
}
app.service("restService", function($http, $q) {
var service = {
get: function () {
var deferredAbort = $q.defer();
var request = $http.get(url, { timeout: deferredAbort.promise } );
promise.abort = function () {
deferredAbort.resolve();
}
return promise;
};
};
return service;
}
app.controller("MyController", function($scope, dataService) {
var promise;
$scope.loadAndAbortData = function () {
promise = dataService.getData();
promise.then(updateUI);
};
$scope.abort = function () {
if (promise) {
promise.abort();
}
};
}
app.service("dataService", function(restService) {
var service = {
getData: function () {
var promise = restService.get();
promise.then(function (response) {
var modifiedData = modifyData(response.data);
return modifiedData;
}, function (response) {
handleError(response.data);
$q.reject(response.data);
});
return promise;
};
};
return service;
}
Upvotes: 0
Views: 1286
Reputation: 8948
I ended up with a solution that "overrides" the then()
function of the promise, so that the abort()
function will follow along through all the layers, independent of how many times then()/catch()/finally()
is called on the promise.
I would really appreciate some input, if someones finds a better solution or sees flaws in this one.
app.service("restService", function($http, $q) {
function createAbortablePromise(promise, deferredAbort) {
promise.abort = function () {
deferredAbort.resolve();
};
// A problem with adding the abort function to the promise is that as soon as someone
// calls then() on this promise (somewhere else in our application), another new promise
// is returned. This new promise, will not have the abort() function. We can solve this
// by "overriding" then() recursively.
var originalThen = promise.then;
promise.then = function (callback, errback, progressback) {
// Invoke the original then(). It will return a new promise
var newPromise = originalThen(callback, errback, progressback);
// This new promise needs an abort function as well.
newPromise = createAbortablePromise(newPromise, deferredAbort);
return newPromise;
};
return promise;
}
var service = {
get: function () {
var deferredAbort = $q.defer();
var request = $http.get(url, { timeout: deferredAbort.promise } );
promise.abort = function () {
deferredAbort.resolve();
}
promise = createAbortablePromise(promise, deferredAbort);
return promise;
};
};
return service;
}
Upvotes: 0
Reputation: 19288
Joel, I'm not 100% sure on this but I'll give it a go based on what you have already tried.
The whole thing seems to hinge on providing, in the RestService, a resolvable timeout: Promise
option in the $http()
config map, and somehow returning a promise with an .abort()
method in addition to its standard methods; then making sure that the .abort()
method "inherit" through the DataService, into the Controller. A couple of little tricks are required to make this happen.
I'm guessing you can do this :
//RestService:
app.service("restService", function($http, $q) {
return {
get: function () {
var dfrd = $q.defer(),
promise = $http.get(url, {
timeout: dfrd.promise
});
//attach the deferred's resolve method as promise's abort method
promise.abort = dfrd.resolve.bind(dfrd);//(or with an explicit function wrapper, as in the question)
return promise;
},
};
}
Similarly, in the DataService, the returned promise needs an abort
method, which is again a reference back to the dfrd.resolve
method back in the RestService.
//DataService
app.service("dataService", function(restService) {
return {
getData: function () {
//Here, the natural syntax would be `return restService.get().then(...)`,
//however a reference to intermediate `restService.get()` is required such that its special `.abort()` method can be attached as the `.abort()` method of the eventually returned promise.
var promise = restService.get();
var promise_ = promise.then(function(response) {
var modifiedData = modifyData(response.data);
return modifiedData;
}, function (response) {
handleError(response.data);
$q.reject(response.data);
});
promise_.abort = promise.abort;//attach promise's abort method as promise_'s abort method.
return promise_;
}
};
});
Thus, dataService.getData()
should deliver a promise with an abort
method into the Controller.
//Controller
app.controller("MyController", function($scope, dataService) {
var promise;
//Here, play the same trick as in the DataService -
//ie. assign the intermediate promise rather than the output of the full chain.
$scope.loadAndAbortData = function () {
promise = dataService.getData();
promise.then(updateUI);
};
$scope.abort = function () {
if (promise && promise.abort) {
promise.abort();
}
};
}
Upvotes: 1