Вложенные обещания

У меня есть функция, которая выполняет серию асинхронных действий, которые, в свою очередь, выполняют циклы других асинхронных действий. Я хочу знать, когда все будет готово. Казалось, что это прекрасное время, чтобы сосредоточиться на обещаниях.

Мой код в состоянии до-обещания сводится к примерно следующему (надеюсь, в процессе упрощения я не сделал этот пример бесполезным):

myClass.prototype.doMaintenance = function() {
    var types = ['choreType1', 'choreType2'];

    types.forEach(function(choreType) {
        // find all chores of the type with score 0 (need to be done) 
        redisClient.zrangebyscore('chores:'+choreType, 0, 0, function(err, chores) {
            if (!err) {
                chores.foreach(function(chore) {
                    doChore(chore, function(err, result){
                        // chore complete!
                    });
                })
            }
        });
    });
}

Я прохожу цикл, и для каждого элемента в цикле я выполняю асинхронный вызов базы данных и просматриваю возвращенные результаты, выполняя еще один асинхронный вызов для каждого результата. Использование обратных вызовов для передачи уведомления о том, что все дела выполнены, кажется, в лучшем случае уродливым. Поэтому моя цель: создать обещание, которое разрешится, когда все дела будут выполнены.

Я столкнулся с двумя трудностями. Один из них - просто получить правильный синтаксис обещания. Я покажу вам, что я пробовал, ниже. Во-первых, проблема, которая может сделать это неразрешимым: предположим, что первый запрос к базе данных возвращается с единственной рутинной работой. Я (как-то) выразил это как часть общего обещания «все дела выполнены». Теперь я возвращаюсь, чтобы получить список следующего вида работы. Что, если к тому времени первая работа будет завершена? Обещание выполнить все работы по дому будет выполнено и будет выполнено до того, как будут добавлены остальные обязанности.

Я использую Q библиотеку в node.js среде. Я использую Redis, но это может быть любой асинхронный источник данных.

myClass.prototype.doMaintenance = function() {
    var types = ['choreType1', 'choreType2'];
    var typePromises = [];

    types.forEach(function(choreType) {
        // find all chores of the type with score 0 (need to be done)
        Q.npost(redisClient, 'zrangebyscore', ['chores:'+choreType, 0, 0]).done(chores) {
            var chorePromises = [];
            chores.foreach(function(chore) {
                chorePromises.push(doChore(chore)); // doChore returns a promise
            });
            typePromises.push(Q.all(chorePromises));
        });
    });

    return Q.all(typePromises); // at this point, typePromises is empty. Bummer!
}

То, что я пытался построить (пока еще не совсем), - это обещание, представляющее собой набор typePromises, которые, в свою очередь, являются коллекциями chorePromises.

Я думаю, что мне нужна структура, которая гласит: «Я обещаю предоставить вам обещание выполнить все работы, как только оно будет доступно». У меня начинает взорваться голова. Будем очень признательны за любые рекомендации (включая использование полностью другого шаблона).


person Jerry    schedule 11.12.2013    source источник
comment
В моем текущем проекте (не node.js) также используется система обещаний. Можете ли вы подтвердить, что ваш подход работает, по крайней мере теоретически, без учета возможности досрочного завершения работы по дому? Если это так, я бы просто добавил еще одно обещание, которое вы завершите в конце своей функции doMaintenance, чтобы убедиться, что цикл завершен до того, как будут выполнены все обещания. Я использую его для асинхронной загрузки ресурсов в систему, и у меня он отлично работает.   -  person Kiruse    schedule 11.12.2013


Ответы (1)


Вы создаете список typePromises асинхронно - и когда вы вызываете Q.all(typePromises), он все еще пуст. Вместо этого вам немедленно нужно вернуть обещание для результата базы данных, которое вы можете немедленно собрать в список. Если вы еще не знаете, каким будет возвращаемое значение этих обещаний - не беспокойтесь, используйте then для составления задач, таких как получение Q.all(chorePromises) после получения результата redis.

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

myClass.prototype.doMaintenance = function() {
    var types = ['choreType1', 'choreType2'];
    var typePromises = types.map(function(choreType) {
        // find all chores of the type with score 0 (need to be done)
        return Q.npost(redisClient, 'zrangebyscore', ['chores:'+choreType, 0, 0]).then(function(chores) {
            var chorePromises = chores.map(doChore); // doChore returns a promise
            return Q.all(chorePromises);
        }); // then returns a promise
    });
    return Q.all(typePromises);
}
person Bergi    schedule 11.12.2013
comment
Спасибо за быстрый ответ! Сейчас тестирую, прорабатываю синтаксис (для меня это еще не вторая натура). Получение unexpected token { после then прямо сейчас, но уже map добавлено в мой репертуар. - person Jerry; 11.12.2013
comment
Уточнен синтаксис, намного ближе к земле обетованной. Спасибо! Моя оставшаяся проблема может быть связана с тем, что иногда нет дел по дому. Я думаю, что в этом случае мне нужно вернуть предварительно обработанное обещание, может быть? Или Q.all([]) это уже делает? - person Jerry; 11.12.2013
comment
Afaik Q.all([]) делает это, да :-) - person Bergi; 12.12.2013