Как правильно закодировать условную (?) promises с Q.js

Я до сих пор не получил полного понимания promises, так что извиняюсь, если это простое недоразумение.

У меня есть функция для удаления элемента на странице, но у меня есть определенное поведение в зависимости от состояния страницы. В коде Psuedo это примерно так:

Does the page have changes?
    If yes - prompt to save changes first
         If yes - save changes
         If no - exit function
    If no - continue
Prompt to confirm delete
    If yes - delete item and reload data
    If no - exit function

Надеюсь, это имеет смысл. По сути, если есть изменения, данные должны быть сохранены первыми. Затем, если данные были сохранены или если не было никаких изменений для начала, попросите пользователя подтвердить удаление. Проблема в том, что я использую durandal и breeze, и я не могу заставить цепочку promises правильно возвращаться.

Моя функция в настоящее время выглядит так, что я знаю неправильно, но я изо всех сил пытаюсь решить, где ее исправить.

if (this.hasChanges()) {
    app.showMessage('Changes must be saved before removing external accounts.  Would you like to save your changes now?', 'Unsaved Changes...', ['Yes', 'No'])
        .then(function (selectedOption) {
             if (selectedOption === 'Yes') {
                 return this.save();
             } else {
                 Q.resolve()
             }
         });
}
app.showMessage('Are you sure you want to delete this item?', 'Delete?', ['Yes', 'No'])
    .then(function (selectedOption) {
        if (selectedOption === 'Yes') {
            item.entityAspect.setDeleted();
            datacontext.saveChanges()
                .then(function () {
                    logger.logNotificationInfo('Item deleted.', '', router.activeInstruction().config.moduleId);
                    Q.resolve(this.refresh(true));
                }.bind(this));
            }
       }.bind(this));

Призыв app.showMessage от durandal возвращает обещание, затем this.save возвращает обещание, и, наконец, this.refresh также возвращает обещание.

Итак, я думаю, мне нужно проверить hasChanges, затем, если необходимо, сохранить вызов и разрешить его. Затем, после того, как условная секция завершит решение, вызовите второе приглашение и затем разрешите все promises внутри этого.

Извините, я не думаю, что это очень ясно, но я тоже думаю, что я не полностью слежу за цепями здесь.

Любая помощь очень ценится! Спасибо.

Ответ 1

Крис верен. Вам не нужны никакие вызовы Q.resolve.

Btw, возвращая обещание с разрешенным значением true или false, не имеет смысла в вашей ситуации. Я боюсь, что вы ошибаетесь, что возвращение false будет препятствовать вызову прикованного then(). Не так! Разрешенное обещание со значением false по-прежнему является хорошим обещанием... как видно из следующего кода, который запускает окно предупреждения:

Q(false) // same as Q.resolve(false)
 .then(function () { alert('resolve(false) triggered then()') })

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


Я не знаю, что this в вашем коде, но это будет ничего, кроме проблемы при выполнении внутренних функций. Захватите его в переменной, чтобы вы не потерялись и не справились с компенсацией логики bind(this).


Я не совсем уверен, что вы пытаетесь сделать. Похоже, что вы не будете продолжать удаление элемента, пока есть несохраненные изменения. Вы сохраните несохраненные изменения, если пользователь это подтвердит. Затем вы попросите пользователя подтвердить удаление. Если пользователь отказывается сохранять ожидающие изменения, вы даже не должны начинать процесс удаления.

Если я правильно понимаю, я думаю, что вы хотите что-то вроде этого:

var self = this; // WHAT IS THIS? I don't know but capture it as 'self'

function saveBeforeDeleting() {
  return saveIfNeeded().then(deleteIfConfirmed);
}

function saveIfNeeded() {
  // no need to save; return resolved promise
  if (!self.hasChanges()) return Q();

  var dialogPromise = app.showMessage(
    'Changes must be saved before removing external accounts. '+
    'Would you like to save your changes now?', 
    'Unsaved Changes...', ['Yes', 'No']
  );

  // When the user replies, either save or return a rejected promise
  // (which stops the flow)
  return dialogPromise.then(function (selectedOption) {
    return (selectedOption === 'Yes') ? self.save() : Q.reject();
  });
}

function deleteIfConfirmed() {
  var dialogPromise = app.showMessage(
    'Are you sure you want to delete this item?', 
    'Delete?',
    ['Yes', 'No']
  );

  return dialogPromise.then(function (selectedOption) {
    return (selectedOption === 'Yes') ? deleteIt() : Q.reject();
  });

  function deleteIt() {
     item.entityAspect.setDeleted();
     return datacontext.saveChanges().then(logAndRefresh);
  }

  function logAndRefresh() {
     logger.logNotificationInfo(
       'Item deleted.',
       '', 
       router.activeInstruction().config.moduleId
     );
     return self.refresh(true));
  }
}

Очевидно, я не тестировал этот код. Подумайте об этом как о вдохновении.

Ответ 2

Если вы произнесете ошибку в обещании, процесс перейдет прямо к первому обработчику .fail/.catch, пропуская любой .thens() между ними.

function AbortError() {}

MyClass.prototype.delete = function() {
    var p = Q();
    var self = this;
    if( this.hasChanges() ) {
        p = app.showMessage('...', '...', ['Yes', 'No'])
        .then(function(answer){
            if( answer === "Yes") {
                return self.save(); //I assume save returns a promise
            }
            throw new AbortError();
        });
    }
    return p
    .then(function() {
        return app.showMessage('...', '...', ['Yes', 'No'])
    })
    .then(function(answer) {
        if( answer === "yes") {
            item.entityAspect.setDeleted();
            return datacontext.saveChanges();
        }
        throw new AbortError();
    })
    .then(function(){
        logger.logNotificationInfo('Item deleted.', '', router.activeInstruction().config.moduleId);
        self.refresh(true);
    })
    .fail(function(e){
        //kris please provide typed .catch feature :(
        if( !(e instanceof AbortError) ) {
            throw e;
        }
    });
};

Ответ 3

В общем, вы хотите создавать функции для выполнения своей работы, которые ВСЕГДА возвращают обещание, даже если это немедленно разрешенный, т.е. "вернуть Q.resolve(someData)".

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

function complexSave() {
   return saveIfNeeded().then(confirmDelete);
}

// returns a promise
function saveIfNeeded() {
  if (this.hasChanges()) {
    return app.showMessage('Changes must be saved before removing external accounts.  Would you like    to  save your changes now?', 'Unsaved Changes...', ['Yes', 'No']).
      then(function (selectedOption) {
         if (selectedOption === 'Yes') {
             return this.save();
         } else {
             return Q.resolve(false)
         }
     });
  else {
    return Q.resolve(false);
  }
}

// returns a promise
function confirmDelete() {
  return app.showMessage('Are you sure you want to delete this item?', 'Delete?', ['Yes', 'No'])
    .then(function (selectedOption) {
       if (selectedOption === 'Yes') {
          item.entityAspect.setDeleted();
          return datacontext.saveChanges()
            .then(function () {
                logger.logNotificationInfo('Item deleted.', '', router.activeInstruction().config.moduleId);
                return Q.resolve(this.refresh(true));
            }.bind(this));
        } else {
          return Q.resolve(false);
        }
   }.bind(this));
}