Прервать запрос AngularJS $http, глубоко вложенный в несколько сервисных вызовов

Я использую отложенное обещание, чтобы прервать запрос $http (как описано в этот пост).

Однако я не уверен, как мне распространять функцию abort(), добавленную на корневой уровень. Каждый раз, когда я вызываю .then() для обещания, возвращается новое обещание (без функции abort()).

Смотрите мой пример ниже. У меня есть контроллер, вызывающий службу, которая, в свою очередь, вызывает другую службу (где делается запрос $http). Пример не работает, так как promise в MyController не совпадает с тем, что было возвращено в restService, и поэтому не имеет функции с именем abort().

  1. Как я могу распространить функцию abort(), чтобы она была доступна в MyController?
  2. Могу ли я полностью вернуть исходное обещание и просто вызвать для него then() (как в примерах внизу)? Что происходит с вызовами then()? Они по-прежнему вызываются «синхронно»?

Контроллер

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

Это решение для DataService и 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;
}

person Joel    schedule 25.06.2014    source источник
comment
в первом варианте обещание тура не получает обещание, поступающее с сервера, но результат обещаний, полученных в результате updateui, очень важен, так как если вы хотите получить доступ к методу прерывания, вы назначаете исходное обещание, поступающее от вашего сервиса   -  person Dayan Moreno Leon    schedule 25.06.2014


Ответы (2)


Джоэл, я не уверен в этом на 100%, но я попробую, основываясь на том, что вы уже пробовали.

Все это, похоже, зависит от предоставления в RestService разрешимой опции timeout: Promise в карте конфигурации $http() и каким-то образом возврата обещания с помощью метода .abort() в дополнение к его стандартным методам; затем убедитесь, что метод .abort() «наследуется» через DataService в контроллер. Для этого потребуется пара маленьких хитростей.

Я предполагаю, что вы можете сделать это:

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

Точно так же в DataService возвращаемое обещание нуждается в методе abort, который снова является ссылкой на метод dfrd.resolve в 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_;
        }
    };
});

Таким образом, dataService.getData() должен доставить промис с методом abort в контроллер.

//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();
        }
    };
}
person Roamer-1888    schedule 25.06.2014
comment
Да, в принципе, я так и сделал сначала. Однако я надеялся, что смогу исправить это, не вмешиваясь в средний слой. Я добавлю ответ с тем, как я закончил - person Joel; 01.07.2014

Я пришел к решению, которое «переопределяет» функцию then() обещания, так что функция abort() будет выполняться через все уровни, независимо от того, сколько раз then()/catch()/finally() вызывается в обещании.

Я был бы очень признателен за ваш вклад, если кто-то найдет лучшее решение или увидит недостатки в этом.

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;
}
person Joel    schedule 01.07.2014