Порядок промисов в 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 я могу получить доступ к объекту конфигурации в ответе, чтобы использовать некоторые временные метки или другие идентификаторы для ручного упорядочения входящих ответов. Однако я надеялся, что смогу просто сказать angular как-то отменить устаревшее обещание и, таким образом, не запускать функцию .then(), когда она будет разрешена.

Это не работает без ручной реализации для $q-промисов вместо $http.

Может быть, стоит просто сразу отказаться от обещания? Но в обоих случаях это может занять вечность, пока, наконец, обещание не будет разрешено до того, как будет сгенерирован следующий запрос (что тем временем приводит к пустому представлению).

Есть ли какая-то угловая API-функция, которую мне не хватает, или существуют надежные шаблоны проектирования или «трюки» с цепочкой обещаний или $q.all для обработки нескольких обещаний, которые возвращают «одинаковые» данные?


person H W    schedule 08.07.2016    source источник
comment
Не уверен, что вы используете для API, но в моем API .Net я использовал SignalR для обработки таких сценариев.   -  person jbrown    schedule 08.07.2016
comment
Отличный вопрос, жду ответов. Но не будет ли намного проще обрабатывать логику внутри .then вместо того, чтобы пытаться избежать разрешения промиса?   -  person user2263572    schedule 08.07.2016


Ответы (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 можно также применять для предотвращения дублирования запросов. Например, в методе load(module, id) моей службы Data я выполняю небольшой процесс следующим образом:

  1. создать requestId на основе параметров URL +.
  2. проверить хеш-таблицу запросов для requestId

    • if requestId is not found: generate new request and store promise in hash-table
    • если requestId найдено: просто верните обещание из хеш-таблицы
  3. Когда запрос завершится, удалите запись requestId из хеш-таблицы.

person plong0    schedule 30.07.2016
comment
Большое спасибо! Я приму этот ответ, поскольку он на самом деле относится к angularJS, о чем я и просил. Ответ Redu содержит рабочий пример для этой техники (без использования Angular) - person H W; 01.08.2016

Отмена обещания просто заставляет его не вызывать функции 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));

person Redu    schedule 29.07.2016

Я все еще пытаюсь найти хороший способ модульного тестирования, но вы можете попробовать такую ​​стратегию:

var canceller = $q.defer();
service.sendCalculationRequest = function () {
    canceller.resolve();
    return $http({
        method: 'GET',
        url: '/do-calculation',
        timeout: canceller.promise
    });
};
person Sam Berry    schedule 31.07.2016
comment
Я нашел это в другой теме после того, как я разместил этот вопрос. Однако это по-прежнему работает только для вызовов $http. - person H W; 01.08.2016
comment
А, я вижу, значит, вы ищете что-то с $resource. - person Sam Berry; 07.08.2016

В промисах 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 (хотя я его не тестировал).

person jmealy    schedule 01.08.2016
comment
у этого могут быть некоторые другие очень полезные приложения, но с помощью этого метода я получаю случайные данные (в зависимости от времени, когда промисы должны быть разрешены) вместо самых последних. Так что это не отвечает на вопрос, если я что-то упустил. - person H W; 01.08.2016