Заказ обещаний в AngularJS
Вопрос:
Есть ли "простой" способ отменить ($q
- /$http
-) обещания в AngularJS или определить порядок выполнения обещаний?
пример
У меня есть долгосрочные расчеты, и я запрашиваю результат через $http
, Некоторые действия или события требуют от меня перезапуска расчета (и, таким образом, отправки нового $http
запрос), прежде чем первоначальное обещание будет решено. Таким образом, я представляю, что не могу использовать простую реализацию, такую как
$http.post().then(function(){
//apply data to view
})
потому что я не могу гарантировать, что ответы возвращаются в том порядке, в котором я отправлял запросы - в конце концов, я хочу показать результат последних вычислений, когда все обещания были разрешены правильно.
Однако я хотел бы избежать ожидания первого ответа, пока я не отправлю новый запрос, подобный этому:
const timeExpensiveCalculation = function(){
return $http.post().then(function(response){
if (isNewCalculationChained) {return timeExpensiveCalculation();}
else {return response.data;}
})
}
Мысли:
Когда используешь $http
я могу получить доступ к config-объекту в ответе, чтобы использовать некоторые временные метки или другие идентификаторы, чтобы вручную упорядочить входящие ответы. Однако я надеялся, что смогу просто сказать angular как-то отменить устаревшее обещание и, таким образом, не запускать функцию.then(), когда она будет решена.
Это не работает без ручной реализации для $q
-обещает вместо $http
хоть.
Может быть, просто отказаться от обещания - это путь? Но в обоих случаях может потребоваться вечность, пока, наконец, не будет выполнено обещание, прежде чем будет сгенерирован следующий запрос (что в свою очередь приводит к пустому представлению).
Есть ли какая-то угловая API-функция, которую мне не хватает, или есть надежные шаблоны проектирования или "хитрости" с цепочкой обещаний или $q.all для обработки нескольких обещаний, которые возвращают "одни и те же" данные?
4 ответа
Я делаю это, генерируя requestId
и в обещании then()
функция, которую я проверяю, если ответ приходит от самых последних requestId
,
Хотя этот подход на самом деле не отменяет предыдущие обещания, он обеспечивает быстрый и простой способ убедиться, что вы обрабатываете ответ на самый последний запрос.
Что-то вроде:
var activeRequest;
function doRequest(params){
// requestId is the id for the request being made in this function call
var requestId = angular.toJson(params); // I usually md5 hash this
// activeRequest will always be the last requestId sent out
activeRequest = requestId;
$http.get('/api/something', {data: params})
.then(function(res){
if(activeRequest == requestId){
// this is the response for last request
// activeRequest is now handled, so clear it out
activeRequest = undefined;
}
else {
// response from previous request (typically gets ignored)
}
});
}
Изменить: В дополнение, я хотел бы добавить, что эта концепция отслеживания requestId's
также может применяться для предотвращения дублирования запросов. Например, в моем Data
Сервисы load(module, id)
метод, я делаю небольшой процесс, как это:
- генерировать
requestId
на основе URL + параметры. проверить в запросах хэш-таблицу для
requestId
- если
requestId
не найдено: сгенерировать новый запрос и сохранить обещание в хэш-таблице - если
requestId
найдено: просто верните обещание из хеш-таблицы
- если
Когда запрос завершится, удалите
requestId
запись из хеш-таблицы.
Отмена обещания просто заставляет его не ссылаться на onFulfilled
а также onRejected
функции на then
этап. Так как @user2263572 упомянул, всегда лучше отпустить обещание, которое не было отменено (собственные обещания ES6 не могут быть отменены в любом случае), и обработать это условие в пределах его then
этап (например, игнорирование задачи, если для глобальной переменной задано значение 2, как показано в следующем фрагменте), и я уверен, что вы можете найти множество других способов сделать это. Одним из примеров может быть;
Извините, что я использую v
(выглядит как символ проверки) для resolve
а также x
(очевидно) для reject
функции.
var prom1 = new Promise((v,x) => setTimeout(v.bind(null,"You shall not read this"),2000)),
prom2,
validPromise = 1;
prom1.then(val => validPromise === 1 && console.log(val));
// oh what have i done..!?! Now i have to fire a new promise
prom2 = new Promise((v,x) => setTimeout(v.bind(null,"This is what you will see"),3000));
validPromise = 2;
prom2.then(val => validPromise === 2 && console.log(val));
Я все еще пытаюсь найти хороший способ для юнит-тестирования, но вы можете попробовать такую стратегию:
var canceller = $q.defer();
service.sendCalculationRequest = function () {
canceller.resolve();
return $http({
method: 'GET',
url: '/do-calculation',
timeout: canceller.promise
});
};
В обещаниях ECMA6 есть Promise.race(promiseArray)
метод. Это принимает массив обещаний в качестве аргумента и возвращает одно обещание. Первое обещание разрешить в массиве передаст его разрешенное значение .then
возвращенного обещания, в то время как другие обещания массива, которые пришли вторыми и т. д., не будут ожидаться.
Пример:
var httpCall1 = $http.get('/api/something', {data: params})
.then(function(val) {
return {
id: "httpCall1"
val: val
}
})
var httpCall2 = $http.get('/api/something-else', {data: params})
.then(function(val) {
return {
id: "httpCall2"
val: val
}
})
// Might want to make a reusable function out of the above two, if you use this in Production
Promise.race([httpCall1, httpCall2])
.then(function(winningPromise) {
console.log('And the winner is ' + winningPromise.id);
doSomethingWith(winningPromise.val);
});
Вы можете использовать это с полифилом Promise или заглянуть в q.race, который кто-то разработал для Angular (хотя я его не тестировал).