Использование success / error / finally / catch с обещаниями в AngularJS

112

Я использую $httpAngularJs, и я не уверен, как использовать возвращенное обещание и обрабатывать ошибки.

У меня есть такой код:

$http
    .get(url)
    .success(function(data) {
        // Handle data
    })
    .error(function(data, status) {
        // Handle HTTP error
    })
    .finally(function() {
        // Execute logic independent of success/error
    })
    .catch(function(error) {
        // Catch and handle exceptions from success/error/finally functions
    });

Это хороший способ сделать это или есть более простой способ?

Joel
источник

Ответы:

103

Обещания - это абстракция над операторами, которые позволяют нам выражать себя синхронно с помощью асинхронного кода. Они представляют собой выполнение разовой задачи.

Они также обеспечивают обработку исключений, как и обычный код, вы можете выполнить возврат из обещания или выбросить.

В синхронном коде вам нужно:

try{
  try{
      var res = $http.getSync("url");
      res = someProcessingOf(res);
  } catch (e) {
      console.log("Got an error!",e);
      throw e; // rethrow to not marked as handled
  }
  // do more stuff with res
} catch (e){
     // handle errors in processing or in error.
}

Обещанная версия очень похожа:

$http.get("url").
then(someProcessingOf).
catch(function(e){
   console.log("got an error in initial processing",e);
   throw e; // rethrow to not marked as handled, 
            // in $q it's better to `return $q.reject(e)` here
}).then(function(res){
    // do more stuff
}).catch(function(e){
    // handle errors in processing or in error.
});
Бенджамин Грюнбаум
источник
4
Как бы вы использовали success(), error()и в finally()сочетании с catch()? Или мне нужно использоватьthen(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp);
Джоэл
3
@Joel вообще, вы не хотите , чтобы когда - либо использовать successи error(предпочитают .thenи .catchвместо этого, вы можете (и должны) опустить errorFunctionот .thenиспользования переменного тока , catchкак в моем коде выше).
Бенджамин Грюнбаум
@BenjaminGruenbaum, не могли бы вы объяснить, почему вы предлагаете избегать success/ error? Также мой Eclipse выходит из себя .catch(, когда видит , поэтому я использую ["catch"](сейчас. Как я могу приручить Eclipse?
Giszmo
Реализация модуля $ http библиотеки $ q в Angular использует .success и .error вместо .then и .catch. Однако в моих тестах я мог получить доступ ко всем свойствам обещания $ http при использовании обещаний .then и .catch. Также см. Ответ zd333.
Steve K
3
@SirBenBenji $ д не имеет .successи .error, $ HTTP возвращает $ Q обещание с добавлением из successи errorобработчиков - однако, эти обработчики не цепь и следует избегать , если / когда это возможно. В общем - если у вас есть вопросы, лучше задавать их как новый вопрос, а не как комментарий к старому.
Бенджамин Грюнбаум
43

Забудьте об использовании successи errorметоде.

Оба метода устарели в angular 1.4. По сути, причина устаревания заключается в том, что они не подходят для цепочки , так сказать.

В следующем примере я попытаюсь продемонстрировать, что я имею в виду, successи errorне использовать цепочки . Предположим, мы вызываем API, который возвращает объект пользователя с адресом:

Пользовательский объект:

{name: 'Igor', address: 'San Francisco'}

Вызов API:

$http.get('/user')
    .success(function (user) {
        return user.address;   <---  
    })                            |  // you might expect that 'obj' is equal to the
    .then(function (obj) {   ------  // address of the user, but it is NOT

        console.log(obj); // -> {name: 'Igor', address: 'San Francisco'}
    });
};

Что произошло?

Поскольку successи errorвозвращает исходное обещание , то есть то, которое возвращает $http.get, объект, переданный в обратный вызов, thenпредставляет собой весь пользовательский объект, то есть тот же самый ввод для предыдущего successобратного вызова.

Если бы мы связали два then, это было бы менее запутанно:

$http.get('/user')
    .then(function (user) {
        return user.address;  
    })
    .then(function (obj) {  
        console.log(obj); // -> 'San Francisco'
    });
};
Майкл П. Базос
источник
1
Также стоит отметить , что successи errorбудут только добавлены к немедленному возвращению на $httpвызов (не прототип), поэтому , если вы звоните другой способ посыла между ними (как, обычно называют return $http.get(url)завернутые в базовой библиотеке, но позже решили переключить счетчик в вызов библиотеки с помощью return $http.get(url).finally(...)), то у вас больше не будет этих удобных методов.
drzaus
35

Я думаю, что предыдущие ответы верны, но вот еще один пример (только fyi, success () и error () устарели в соответствии с главной страницей AngularJS :

$http
    .get('http://someendpoint/maybe/returns/JSON')
    .then(function(response) {
        return response.data;
    }).catch(function(e) {
        console.log('Error: ', e);
        throw e;
    }).finally(function() {
        console.log('This finally block');
    });
grepit
источник
3
Насколько мне известно, наконец, не возвращается ответ.
диплозавр
11

Какой тип детализации вы ищете? Обычно вы можете обойтись:

$http.get(url).then(
  //success function
  function(results) {
    //do something w/results.data
  },
  //error function
  function(err) {
    //handle error
  }
);

Я обнаружил, что "finally" и "catch" лучше использовать при объединении нескольких обещаний.

джастин
источник
1
В вашем примере обработчик ошибок обрабатывает только ошибки $ http.
Бенджамин Грюнбаум
1
Да, мне все еще нужно обрабатывать исключения в функциях успеха / ошибки. И затем мне нужен какой-то общий обработчик (где я могу установить такие вещи, как loading = false)
Джоэл
1
У вас есть фигурная скобка вместо круглых скобок, закрывающая ваш вызов then ().
Пол МакКлин
1
Это не работает с ошибками ответа 404, работает только с .catch()методом
elporfirio
Это правильный ответ для обработки HTTP-ошибок, возвращаемых контроллерам
Леон,
5

В случае Angular $ http функции success () и error () будут иметь развернутый объект ответа, поэтому подпись обратного вызова будет похожа на $ http (...). Success (function (data, status, headers, config))

для then () вы, вероятно, будете иметь дело с необработанным объектом ответа. например, опубликованный в документе AngularJS $ http API

$http({
        url: $scope.url,
        method: $scope.method,
        cache: $templateCache
    })
    .success(function(data, status) {
        $scope.status = status;
        $scope.data = data;
    })
    .error(function(data, status) {
        $scope.data = data || 'Request failed';
        $scope.status = status;
    });

Последний .catch (...) не понадобится, если в предыдущей цепочке обещаний не возникнет новая ошибка.

zd333
источник
2
Методы успеха / ошибки устарели.
OverMars 03
-3

Я делаю это, как предлагает Брэдли Брейтуэйт в своем блоге :

app
    .factory('searchService', ['$q', '$http', function($q, $http) {
        var service = {};

        service.search = function search(query) {
            // We make use of Angular's $q library to create the deferred instance
            var deferred = $q.defer();

            $http
                .get('http://localhost/v1?=q' + query)
                .success(function(data) {
                    // The promise is resolved once the HTTP call is successful.
                    deferred.resolve(data);
                })
                .error(function(reason) {
                    // The promise is rejected if there is an error with the HTTP call.
                    deferred.reject(reason);
                });

            // The promise is returned to the caller
            return deferred.promise;
        };

        return service;
    }])
    .controller('SearchController', ['$scope', 'searchService', function($scope, searchService) {
        // The search service returns a promise API
        searchService
            .search($scope.query)
            .then(function(data) {
                // This is set when the promise is resolved.
                $scope.results = data;
            })
            .catch(function(reason) {
                // This is set in the event of an error.
                $scope.error = 'There has been an error: ' + reason;
            });
    }])

Ключевые моменты:

  • Функция разрешения связана с функцией .then в нашем контроллере, т.е. все в порядке, поэтому мы можем сдержать свое обещание и разрешить его.

  • Функция reject ссылается на функцию .catch в нашем контроллере, т.е. что-то пошло не так, поэтому мы не можем сдержать свое обещание и должны его отклонить.

Это довольно стабильно и безопасно, и если у вас есть другие условия для отклонения обещания, вы всегда можете отфильтровать свои данные в функции успеха и вызвать deferred.reject(anotherReason)причину отказа.

Как предположил Райан Вайс в комментариях , это может оказаться бесполезным, если вы, так сказать, немного не поиграете с ответом.

Поскольку successи errorустарели с версии 1.4, возможно, лучше использовать обычные методы обещаний thenиcatch и преобразовать ответ в этих методах и вернуть обещание этого трансформированного ответа.

Я показываю один и тот же пример с обоими подходами и третьим промежуточным подходом:

successи errorподход ( successи errorвернуть обещание ответа HTTP, поэтому нам нужна помощь $qдля возврата обещания данных):

function search(query) {
  // We make use of Angular's $q library to create the deferred instance
  var deferred = $q.defer();

  $http.get('http://localhost/v1?=q' + query)
  .success(function(data,status) {
    // The promise is resolved once the HTTP call is successful.
    deferred.resolve(data);              
  })

  .error(function(reason,status) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.error){
      deferred.reject({text:reason.error, status:status});
    }else{
      //if we don't get any answers the proxy/api will probably be down
      deferred.reject({text:'whatever', status:500});
    }
  });

  // The promise is returned to the caller
  return deferred.promise;
};

thenи catchподход (это немного сложнее проверить из-за выброса):

function search(query) {

  var promise=$http.get('http://localhost/v1?=q' + query)

  .then(function (response) {
    // The promise is resolved once the HTTP call is successful.
    return response.data;
  },function(reason) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.statusText){
      throw reason;
    }else{
      //if we don't get any answers the proxy/api will probably be down
      throw {statusText:'Call error', status:500};
    }

  });

  return promise;
}

Однако есть половинчатое решение (таким образом вы можете избежать throwи в любом случае вам, вероятно, придется использовать $qдля имитации поведения обещания в ваших тестах):

function search(query) {
  // We make use of Angular's $q library to create the deferred instance
  var deferred = $q.defer();

  $http.get('http://localhost/v1?=q' + query)

  .then(function (response) {
    // The promise is resolved once the HTTP call is successful.
    deferred.resolve(response.data);
  },function(reason) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.statusText){
      deferred.reject(reason);
    }else{
      //if we don't get any answers the proxy/api will probably be down
      deferred.reject({statusText:'Call error', status:500});
    }

  });

  // The promise is returned to the caller
  return deferred.promise;
}

Любые комментарии и исправления приветствуются.

Часовщик
источник
2
Зачем вам использовать $ q, чтобы заключить обещание в обещание. Почему бы просто не вернуть обещание, возвращаемое $ http.get ()?
Ryan Vice
Потому что success()и error()не вернет новое обещание, как это then()делает. С помощью $qмы заставляем нашу фабрику возвращать обещание данных вместо обещания ответа HTTP.
Часовщик
Ваш ответ сбивает меня с толку, так что, возможно, я не очень хорошо объясняюсь. если вы не манипулируете ответом, вы можете просто вернуть обещание, которое возвращает $ http. см. этот пример, который я только что написал: jsbin.com/belagan/edit?html,js,output
Райан
1
Я не вижу ценности. Мне это кажется ненужным, и я отвергаю рецензирование кода моих проектов, в которых используется этот подход, но если вы извлекаете из этого пользу, вам следует его использовать. Я также видел несколько обещаний в статьях о лучших практиках Angular, в которых ненужная упаковка рассматривается как запах.
Ryan Vice
1
Это отложенный антипаттерн . Прочтите, вы
упускаете суть