AngularJS - устойчивость к сбоям в $q.all()

Я пытаюсь заполнить некоторые локальные данные, обрабатывая серию удаленных вызовов.
Когда каждое обещание разрешено, я загружаю данные и продолжаю.

Метод $q.all( [] ) делает именно это:

        $q.all([
            this.getUserInfo(11)
                .then(function (r) {
                    results.push(r)
                }),

            this.getUserConns()
                .then(function (r) {
                    results.push(r)
                }),

            this.getUserCtxs()
                .then(function (r) {
                    results.push(r)
                })
        ])
        .then(function () {
            console.log(results)
        })


Проблема в том, что этот код не является отказоустойчивым.
Если какой-либо из этих вызовов завершится ошибкой, никто не получит рыбу!

Обертывание вызовов в операторе try/catch просто заставляет $q.all() полностью игнорировать запись, даже если она не завершается ошибкой (обратите внимание на console.log в func)...

        $q.all([
            this.getUserInfo2(11)
                .then(function (r) {
                    results.push(r)
                }),

            function () {
                try {
                    this.getUserGroups()
                        .then(function (r) {
                            console.log(r)
                            results.push(r)
                        })
                }
                catch (err) {
                    console.log(err)
                }
            },
        ])
        .then(function () {
            console.log(results)
        })

Выход:

[Объект]


Есть намек на то, как я могу обернуть это, чтобы сделать его устойчивым?


Благодаря @dtabuenc я сделал еще один шаг вперед. Реализуя обратный вызов ошибки, я могу избежать разрыва цепочки и протолкнуть значения разрешенных промисов.

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

Код вызывающего абонента

    return $q.all([

            this.getUserInfo(user_id)
                .then(function (r) {
                    results['personal_details'] = r
                }),

            this.getUserConns()
                .then(
                    function (r) {
                    results['connections'] = r
                    },
                    function(err) {
                        console.log(err)
                    })

        ])
        .then(function () {
            return (results)
        })

Код вызываемого объекта (вставка с исключением)

    getUserConns: function() {

        return __doCall( ws.getUserConnections, {} )
            .then( function(r) {

                // very generic exception injected
                throw new Error

                if (r && r.data['return_code'] === 0) {
                    return r.data['entries']
                }
                else {
                    console.log('unable to retrieve the activity - err: '+r.data['return_code'])
                    return null
                }
            })
    },

person domokun    schedule 13.12.2013    source источник


Ответы (5)


Это будет работать, но также поместит ошибки в массив.

function push(r) {
    results.push(r);
}

$q.all([
    this.getUserInfo(11).then(push).catch(push),
    this.getUserConns().then(push).catch(push),
    this.getUserCtxs().then(push).catch(push)
])
.then(function () {
    console.log(results);
})

Вы также должны улучшить свое понимание промисов, вы никогда не должны использовать try-catch с промисами - при использовании промисов вы используете метод .catch() (все остальное неявно является try). Это работает как для обычных ошибок, так и для асинхронных ошибок.


Если вы хотите полностью игнорировать ошибки:

function push(r) {
    results.push(r);
}

function noop() {}

$q.all([
    this.getUserInfo(11).then(push).catch(noop),
    this.getUserConns().then(push).catch(noop),
    this.getUserCtxs().then(push).catch(noop)
])
.then(function () {
    console.log(results);
})
person Esailija    schedule 15.12.2013
comment
На самом деле, я все еще новичок в Angular, промисах и даже JS, и я более чем счастлив узнавать что-то новое каждый день! Метод .catch() по-прежнему не блокирует исключение, которое я порождал в своем последнем примере, но я думаю, что это самый близкий к моему идеальному результату результат. Спасибо! - person domokun; 16.12.2013
comment
Угловые обещания @domokun неправильно сообщают даже об обнаруженных исключениях, нарушая дух обещаний / A +, если не нарушая спецификацию. - person Esailija; 16.12.2013
comment
Полезно знать... Наверное... Еще раз спасибо! - person domokun; 17.12.2013
comment
Удивительное решение! Я пробовал много способов получить несколько ошибок от $q.all. Это самое короткое и простое для понимания решение, которое я видел до сих пор. Спасибо! - person A-letubby; 28.11.2014
comment
В приведенном выше случае это нарушает последовательность вывода. - person Aniket; 03.06.2015
comment
Я думаю, что есть более компактный и чистый способ сделать это, как я разместил ниже stackoverflow.com/a/33125307/1345244 - person Joël; 14.10.2015
comment
отличное решение - person Sujit Y. Kulkarni; 29.01.2018

Думаю проще сделать:

$q.all([
 mypromise1.$promise.catch(angular.noop),
 mypromise2.$promise.catch(angular.noop),
 mypromise1.$promise.catch(angular.noop)
])
.then(function success(data) {
 //.....
});
person Joël    schedule 14.10.2015

Я не уверен, что вы имеете в виду под устойчивостью. Что вы хотите, чтобы произошло, если одно из обещаний не выполняется?

Ваш try-catch не сработает, потому что обещание не будет выполнено асинхронно.

Однако вы можете передать обработчик ошибок в качестве второго параметра вызова then() и делать там все, что пожелаете.

person Daniel Tabuenca    schedule 13.12.2013
comment
Хорошо, я забыл об обратном вызове ошибки. Он устойчив в том смысле, что цепочка не прерывается, а это то, что я хотел. Однако я не могу скрыть исключение, если я его не поймаю... Я обновлю свой вопрос, чтобы объяснить - person domokun; 13.12.2013
comment
Я не думаю, что вы можете поймать ошибки HTTP. Они всегда будут отображаться на консоли. Это независимо от того, используете ли вы angular или нет. - person Daniel Tabuenca; 13.12.2013
comment
Это правда, и меня это не смущает. Что меня действительно беспокоит, так это отображение исключения на консоли. Пожалуйста, взгляните на мой обновленный вопрос - person domokun; 13.12.2013
comment
Вы можете обернуть его в try/catch, но он будет перехватывать только те исключения, которые происходят внутри потока. Если исключение происходит на обратном пути (например, при возврате из HTTP-запроса, ответа на обработчик событий или setTimeout(), оно не будет перехвачено). Единственный способ поймать их - в коде, который обрабатывает возврат. - person Daniel Tabuenca; 13.12.2013

Та же проблема здесь. Для тех из вас, у кого есть цикл for: внутри ответа then:

var tracks = [];
var trackDfds = [];
for(var i = 0; i < res.items.length; i++){
    var fn = function () {
        var promise = API.tracks(userId, res.items[i].id);
        return promise.then(function (res) {
            if (res.items.length) {
              tracks.push(res.items);
            }
        }).catch(angular.noop);
    };
    trackDfds.push(fn());
}
$q.all(trackDfds)
    .then(function (res) {
        console.log(tracks);
    });
person cameronroe    schedule 17.10.2014

Ответ @Esailija кажется обходным решением проблемы. Вы не можете решить проблему за пределами основного виновника проблемы: $q.

Кажется, немного разумнее иметь обратные вызовы отклонения для каждого then (2-й аргумент) и вставлять туда $q.reject(...).

Пример:

$q.all([
    this.getUserInfo(11).then(
        function (response) { // UI data preparation for this part of the screen }, 
        function (response) {
           $q.reject(response);
        }
    ),
    // ...
])
.then(
    function () {
      // all good
    },
    function () {
      // at least one failed
    }
)

Это особенно заметно, когда модель пользовательского интерфейса зависит от всех вызовов ajax.

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

person Dragos Rusu    schedule 22.10.2015