q.all, кажется, ведет себя по-другому, почему?

Вот мой код, который, как я ожидаю, будет начинаться сверху, а выполнение будет продолжаться вниз. Первые два предложения получают запись из базы данных, а затем обновляют объект «я». Однако, когда выполнение достигает Q.all, метод возвращает промисы, а не фактически разрешает промисы и помещает значение в locs (и, в конечном итоге, «я»). В конце концов обещания будут разрешены, но после "return self" в последнем предложении then. Мне нужно, чтобы self.locations был установлен до запуска «return self».

return dao.getRecords(APP_COLLECTION, filters).then(function(data) {
    return self.loadClientAppImpl(data);
}).then(function() {
    locArray = self.app.field_194_raw;
    return dao.getChildren("object_23", self.appId, "field_210", "field_164").then(function(knackRecord) {
        self.priorCarriers = knackRecord.records;
    });
}).then(function() {
    var locPromiseArray = getPromisesArray(locArray);
    return Q.all(locPromiseArray).then(function(locs){
        self.locations = locs;
    });
}).then(function() {
    return self;
}).done();

Я думал, что множественные промисы должны действовать так же, как и промисы одиночного вызова?

т.е. dao.getRecords в порядке, но почему Q.all([dao.getRecords1, dao.getRecord2 и т. д.]) не работает, как я мог ожидать (т.е. не разрешается до «return self;»)?

Следующий код работает нормально, я заменил Q.all одним обещанием, и все работает, как и ожидалось (т.е. возвращает себя, вызываемого последним в цепочке). Остается вопрос: почему Q.all не работает так же? Я предполагаю, что это именно то, как это должно работать, и я ничего не понимаю в реализации.

return dao.getRecords(APP_COLLECTION, filters).then(function(data) {
    self.loadClientAppImpl(data);
}).then(function() {
    locArray = self.app.field_194_raw;
    return dao.getChildren("object_23", self.appId, "field_210", "field_164").then(function(knackRecord) {
        self.priorCarriers = knackRecord.records;
    });
}).then(function() {
    var locPromiseArray = getPromiseArray(locArray);
    return locPromiseArray[0].then(function(locs){
        self.locations = locs;
    });
}).then(function() {
    return self;
}).done();

Вот метод getPromisesArray

function getPromiseArray (locArray) {
    var locationCollection = "object_22";
    //create a promise for all of the records
    var locationPromiseArray = [];
    if(locArray[0]){
        var locId0 = locArray[0].id;
        //dao.getRecord returns a promise
        locationPromiseArray.push(dao.getRecord(locationCollection, locId0));
    }       

    if(locArray[1]){
        var locId1 = locArray[1].id;
        locationPromiseArray.push(dao.getRecord(locationCollection, locId1));
    }

    if(locArray[2]){
        var locId2 = locArray[2].id;
        locationPromiseArray.push(dao.getRecord(locationCollection, locId2));
    }            

    if(locArray[3]){
        var locId3 = locArray[3].id;
        locationPromiseArray.push(dao.getRecord(locationCollection, locId3));
    }
    return locationPromiseArray;
};

Спасибо за любую помощь!

Отметка

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

return dao.getRecords(APP_COLLECTION, filters).then(function(data) {
    self.loadClientAppImpl(data);
}).then(function() {
    return dao.getChildren("object_23", self.appId, "field_210", "field_164");
}).then(function(knackRecord) {
    self.priorCarriers = knackRecord.records;
    locPromiseArray = getPromiseArray(self.app.field_194_raw);
    return Q.all(locPromiseArray).then(function(results){
            self.locations = results;
            return self;  
    });
});

return dao.getRecords(APP_COLLECTION, filters).then(function(data) {
    self.loadClientAppImpl(data);
}).then(function() {
    return dao.getChildren("object_23", self.appId, "field_210", "field_164");
}).then(function(knackRecord) {
    self.priorCarriers = knackRecord.records;
    locPromiseArray = getPromiseArray(self.app.field_194_raw);
    return Q.all(locPromiseArray);
}).then(function(results){
        self.locations = results;
        return self;  
});

person Mark Waschkowski    schedule 23.07.2014    source источник
comment
Извините, но я не совсем понимаю проблему. В последнем then вы создаете обещание, используя Q.all, а затем — во внутреннем вызове then — разрешаете это обещание со значением self. Затем первый return содержит обещание, которое в конечном итоге будет разрешено со значением self.   -  person Razem    schedule 23.07.2014
comment
Да, точно. Вопрос у меня в порядке исполнения всего этого. У меня есть метод, который вызывает это, и если я удалю последнее предложение «тогда» (тот, в котором есть Q.all), то вызывающий получит «я» после того, как предыдущие два предложения then будут завершены (разрешены). Но с Q.all вызывающая сторона получает неразрешенные промисы. Я хочу, чтобы вызывающий абонент вместо этого получил разрешенные ответы. Я обновлю код, чтобы сделать его немного понятнее   -  person Mark Waschkowski    schedule 23.07.2014
comment
Покажите нам свой getPromiseArray метод. Разве он не возвращает массив обещаний?   -  person Bergi    schedule 23.07.2014
comment
Часть return self; должна произойти после того, как все обещания, перечисленные в Q.all, будут выполнены. Вызывающий должен получить обещание, созданное вызовом done, которое в конечном итоге должно быть выполнено вызовом self. Этот код должен работать именно так. Если он возвращает что-то еще, возможно, у вас другой код.   -  person Razem    schedule 23.07.2014
comment
Спасибо за комментарий Радек. Но это код, и если я установлю точки останова, я увижу, что 'return self;' происходит сразу после вызова 'return Q.all(...)'. И я вижу, что другие вызовы работают нормально (например, «return self.loadClientAppImpl(data);» и «self.priorCarriers = knackRecord.records;»)   -  person Mark Waschkowski    schedule 23.07.2014
comment
Он должен работать следующим образом: 1. getRecords 2. data -> loadClientAppImpl 3. appImpl -> getChildren 4. knackRecord -> self.priorCarriers 5. undefined -> Q.all 6. locs -> self.locations 7. undefined -> return self 8. self -> done Если это действительно делает что-то еще, не могли бы вы привести живой пример?   -  person Razem    schedule 23.07.2014
comment
Если это поможет, когда я устанавливаю точки останова, строка «self.locations = locs» появляется во время некоторого вызова «flush» внутри библиотеки q, что отличается от других предложений!   -  person Mark Waschkowski    schedule 23.07.2014
comment
Радек - да, именно так идут вызовы, до шага 6, а потом вызывается return self, а потом делает self.locations, вздох.   -  person Mark Waschkowski    schedule 23.07.2014
comment
Это странно. Может быть, попробуйте вернуть только Q.all, а вызов then переместится в цепочку до return self;. Если это не сработает, у меня закончились идеи. :D   -  person Razem    schedule 23.07.2014
comment
Между прочим, это было бы намного проще, если бы вы использовали Bluebird и .bind.   -  person Benjamin Gruenbaum    schedule 23.07.2014
comment
@MarkWaschkowski, запуск обещаний концептуально неверен. Возможно, вам нужно вернуться к истокам.   -  person Roamer-1888    schedule 23.07.2014
comment
@ Roamer-1888, возможно, вы правы, я все еще знакомлюсь с Q и обещаниями. Любое понимание обновленного примера и разницы?   -  person Mark Waschkowski    schedule 23.07.2014
comment
Марк, это сложная тема. Сейчас я поглощен Тур де Франс, но посмотрю позже.   -  person Roamer-1888    schedule 23.07.2014
comment
@MarkWaschkowski Как я уже сказал, попробуйте переместить внутренний вызов then в основную цепочку.   -  person Razem    schedule 23.07.2014
comment
@Radek - пробовал, без изменений.   -  person Mark Waschkowski    schedule 23.07.2014


Ответы (2)


Трудно понять, почему замена одного промиса на Q.all(...) дает желаемый результат. Возможно, ваш анализ происходящего неверен.

Тем не менее, вот некоторые идеи.

Промежуточная цепочка .then() не нужна, если только она не фильтруется, возвращая новое обещание или другое значение. Средняя цепочка .then(), чей обратный вызов не делает ничего, кроме присваивания, является кандидатом на отбивку. У вас есть два таких задания.

  • self.priorCarriers = knackRecord.records;
  • self.locations = лок;

В таких вещах легко ошибиться, но я думаю, что ваш основной блок кода будет перестроен следующим образом:

return dao.getRecords(APP_COLLECTION, filters).then(function(data) {
    return self.loadClientAppImpl(data);
}).then(function() {
    return dao.getChildren("object_23", self.appId, "field_210", "field_164");
}).then(function(knackRecord) {
    self.priorCarriers = knackRecord.records;
    return Q.all(getPromisesArray(self.app.field_194_raw));
}).then(function(locs) {
    self.locations = locs;
    return self;
}).done();

Обратите внимание, что присваивания теперь находятся в функциях, которые также что-то возвращают, и, что важно, self.locations = locs находится в той же функции, которая возвращает self. Это может решить вашу проблему, а может и не решить, хотя это многообещающе [так в оригинале].

Для справки, getPromiseArray() также следует упростить следующим образом:

function getPromiseArray (locArray) {
    return locArray.map(function(loc) {
        return dao.getRecord("object_22", loc.id);
    });
};
person Roamer-1888    schedule 23.07.2014
comment
Спасибо за ответ, теперь я использую ваш getPromiseArray, это приятно. Я обновил исходный вопрос, но все еще не уверен, что происходит. Думаю, мне придется сыграть на скрипке, если у вас нет других идей? - person Mark Waschkowski; 24.07.2014
comment
Мы можем думать только о том, что один или несколько ваших dao.getRecord() вызовов терпят неудачу. Попробуйте добавить catch(...) в конце цепочки .then, чтобы зарегистрировать потенциальную ошибку. - person Roamer-1888; 24.07.2014

Оказывается, ответ довольно прост, все дело в типе возвращаемого значения и в том, как промисы настроены для работы, что для Q называется распространением и обсуждается здесь:

https://github.com/kriskowal/q

Цитировать:

var outputPromise = getInputPromise()
  .then(function (input) {
    }, function (reason) {
  });

Если вы вернете значение в обработчике, outputPromise будет выполнен.

Если вы сгенерируете исключение в обработчике, outputPromise будет отклонен.

Если вы вернете промис в обработчике, outputPromise «станет» этим промисом. Возможность стать новым обещанием полезна для управления задержками, объединения результатов или восстановления после ошибок.

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

return dao.getRecords(APP_COLLECTION, filters).then(function(data) {
    self.loadClientAppImpl(data);
}).then(function() {
    locArray = self.app.field_194_raw;
    return dao.getChildren("object_23", self.appId, "field_210", "field_164").then(function(knackRecord) {
        self.priorCarriers = knackRecord.records;
    });
}).then(function() {
    var locPromiseArray = getPromiseArray(locArray);
    return locPromiseArray[0].then(function(locs){
        self.locations = [locs];
    });
}).then(function() {
    return self;
}).done();

В конце концов возвращается 'self', и вызывающая сторона счастлива, потому что это то, что она ожидает, значение. Однако со следующим кодом (из исходного вопроса) значение не возвращается, вместо этого возвращается обещание (второй случай):

return dao.getRecords(APP_COLLECTION, filters).then(function(data) {
    return self.loadClientAppImpl(data);
}).then(function() {
    locArray = self.app.field_194_raw;
    return dao.getChildren("object_23", self.appId, "field_210", "field_164").then(function(knackRecord) {
        self.priorCarriers = knackRecord.records;
    });
}).then(function() {
    var locPromiseArray = getPromisesArray(locArray);
    return Q.all(locPromiseArray).then(function(locs){
        self.locations = locs;
    });
}).then(function() {
    return self;
}).done();

т.е. при использовании Q.all вы получаете обещание, которое необходимо разрешить, тогда как в первом случае возвращается self (значение).

Сейчас я значительно подчистил и оптимизировал код, но я хотел закрыть этот цикл для других на случай, если это может им помочь.

кстати - из кода вызова есть простой способ обойти эту ситуацию, Q предоставляет метод "когда", который вы можете использовать, если вы не уверены, возвращаете ли вы обещание или значение:

return Q.when(valueOrPromise, function (value) {
}, function (error) {
});

В итоге я поставил это на место, чтобы проверить два сценария, и увидел, что переменная valueOrPromise была заполнена либо значением (первый случай), либо обещанием (второй случай).

Спасибо всем, кто способствовал выяснению этого - ура!

person Mark Waschkowski    schedule 25.07.2014
comment
Вы говорите, что dao.getRecord не всегда возвращал промис, и что Q.all() не приводил автоматически не промис-значения? - person Bergi; 25.07.2014
comment
Нет, я говорю, что Q.all возвращается с чем-то отложенным, а не решенным, тогда как ВСЕ остальные промисы были решены. - person Mark Waschkowski; 25.07.2014
comment
Конечно, Q.all возвращает обещание, разрешение которого отложено до тех пор, пока не будут выполнены все введенные обещания? Но для вашего скрипта не должно иметь значения, разрешено ли обещание или все еще находится в ожидании. - person Bergi; 26.07.2014
comment
Как указано в исходном сообщении: в конце концов обещания будут разрешены, но после «возврата себя» в последнем предложении then. Мне нужно, чтобы self.locations был установлен до запуска «return self». - person Mark Waschkowski; 28.07.2014
comment
@Bergi Как указано в исходном сообщении: в конце концов обещания будут разрешены, но после «возврата себя» в последнем предложении then. Мне нужно, чтобы self.locations был установлен до запуска «return self». Этого не происходит при использовании Q.all, и вызывающий код должен так или иначе с этим справиться, вот в чем разница. т.е. то, что возвращает вызывающий код (не показан), отличается в зависимости от того, используется ли Q.all или серия одиночных промисов. Это то, что делает Q.when хорошим - он прозрачно обрабатывает оба этих случая. - person Mark Waschkowski; 28.07.2014