Это не будет полным ответом на ваш вопрос, но, надеюсь, он поможет вам и другим, когда вы попытаетесь прочитать документацию по сервису $q
. Мне потребовалось некоторое время, чтобы понять это.
Давайте на мгновение отложим AngularJS и просто рассмотрим вызовы API Facebook. Оба вызова API используют механизм обратного вызова для уведомления вызывающей стороны о доступности ответа от Facebook:
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
Это стандартный шаблон для обработки асинхронных операций в JavaScript и других языках.
Одна большая проблема с этим шаблоном возникает, когда вам нужно выполнить последовательность асинхронных операций, где каждая последующая операция зависит от результата предыдущей операции. Вот что делает этот код:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
Сначала он пытается войти в систему, а затем, только убедившись, что вход был успешным, он делает запрос к Graph API.
Даже в этом случае, который объединяет всего лишь две операции, все начинает запутываться. Метод askFacebookForAuthentication
принимает обратный вызов для неудачи и успеха, но что происходит, когда FB.login
завершается успешно, а FB.api
терпит неудачу? Этот метод всегда вызывает обратный вызов success
независимо от результата метода FB.api
.
Теперь представьте, что вы пытаетесь закодировать надежную последовательность из трех или более асинхронных операций таким образом, чтобы правильно обрабатывать ошибки на каждом шаге и чтобы через несколько недель он был понятен кому-либо еще или даже вам. Возможно, но очень легко просто продолжать вкладывать эти обратные вызовы и по пути потерять след ошибок.
Теперь давайте на мгновение отложим в сторону API Facebook и просто рассмотрим API Angular Promises, реализованный службой $q
. Паттерн, реализованный этой службой, представляет собой попытку превратить асинхронное программирование обратно в нечто, напоминающее линейную серию простых операторов, с возможностью «генерировать» ошибку на любом этапе пути и обрабатывать ее в конце, семантически аналогично знакомый блок try/catch
.
Рассмотрим этот надуманный пример. Скажем, у нас есть две функции, где вторая функция использует результат первой:
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
Теперь представьте, что выполнение firstFn и secondFn занимает много времени, поэтому мы хотим обработать эту последовательность асинхронно. Сначала мы создаем новый объект deferred
, который представляет собой цепочку операций:
var deferred = $q.defer();
var promise = deferred.promise;
Свойство promise
представляет конечный результат цепочки. Если вы зарегистрируете промис сразу после его создания, вы увидите, что это просто пустой объект ({}
). Пока ничего не видно, идем дальше.
Пока наше обещание представляет собой только начальную точку в цепочке. Теперь добавим две наши операции:
promise = promise.then(firstFn).then(secondFn);
Метод then
добавляет шаг в цепочку, а затем возвращает новое обещание, представляющее конечный результат расширенной цепочки. Вы можете добавить столько шагов, сколько захотите.
Пока что мы настроили нашу цепочку функций, но на самом деле ничего не произошло. Вы начинаете работу, вызывая deferred.resolve
, указывая начальное значение, которое вы хотите передать на первый фактический шаг в цепочке:
deferred.resolve('initial value');
А потом... по-прежнему ничего не происходит. Чтобы убедиться, что изменения модели отслеживаются должным образом, Angular фактически не вызывает первый шаг в цепочке до тех пор, пока не будет вызван следующий раз $apply
:
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
Так что насчет обработки ошибок? До сих пор мы указывали только обработчик успеха на каждом этапе цепочки. then
также принимает обработчик ошибок в качестве необязательного второго аргумента. Вот еще один, более длинный пример цепочки промисов, на этот раз с обработкой ошибок:
var firstFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'firstResult';
}
};
var secondFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'secondResult';
}
};
var thirdFn = function(param) {
// do something with param
return 'thirdResult';
};
var errorFn = function(message) {
// handle error
};
var deferred = $q.defer();
var promise = deferred.promise.then(firstFn).then(secondFn).then(thirdFn, errorFn);
Как видно из этого примера, у каждого обработчика в цепочке есть возможность перенаправить трафик на следующий обработчик ошибки вместо следующего обработчика успеха. В большинстве случаев у вас может быть один обработчик ошибок в конце цепочки, но вы также можете иметь промежуточные обработчики ошибок, которые пытаются восстановить.
Чтобы быстро вернуться к вашим примерам (и вашим вопросам), я просто скажу, что они представляют собой два разных способа адаптации ориентированного на обратный вызов API Facebook к способу наблюдения за изменениями модели в Angular. В первом примере вызов API заключен в обещание, которое можно добавить в область действия и которое понимается системой шаблонов Angular. Второй использует более грубый подход, устанавливая результат обратного вызова непосредственно в области видимости, а затем вызывая $scope.$digest()
, чтобы Angular узнал об изменении из внешнего источника.
Два примера нельзя сравнивать напрямую, потому что в первом отсутствует шаг входа в систему. Однако, как правило, желательно инкапсулировать такие взаимодействия с внешними API в отдельные службы и доставлять результаты контроллерам в виде промисов. Таким образом, вы можете отделить свои контроллеры от внешних проблем и упростить их тестирование с помощью фиктивных сервисов.
person
karlgold
schedule
01.04.2013