简体   繁体   中英

Abort AngularJS $http request, deeply nested in multiple service calls

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() .

  1. How can I propagate the abort() function so that it is available in MyController ?
  2. Can I return the original promise all the way, and just call then() on that (like in the examples at the bottom)?. What happens with the then() calls? Are they still invoked "synchronously"?

Controller

app.controller("MyController", function($scope, dataService) {
    var promise;

    $scope.loadAndAbortData = function () {
        promise = dataService.getData().then(updateUI);
    };

    $scope.abort = function () {
        if (promise) {
            promise.abort();
        }
    };
}

DataService

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;
}

RestService:

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;
}

Is this a solution for DataService and MyController?

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;
}

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();
        }
    };
}

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM