вернет

var d = require("deferred");

var d1 = d();

var promise1 = d1.promise.then(
    function(wins) { console.log('promise1 resolved'); return wins;},
    function(err) { console.log('promise1 rejected'); return err;});
var promise2 = promise1.then(
    function(wins) { console.log('promise2 resolved'); return wins;},
    function(err) { console.log('promise2 rejected'); return err;});
var promise3 = promise2.then(
    function(wins) { console.log('promise3 resolved'); return wins;},
    function(err) { console.log('promise3 rejected'); return err;});
d1.reject(new Error());

Результатом выполнения этой операции является следующий вывод:

promise1 rejected
promise2 resolved
promise3 resolved

Хорошо, для меня этот результат не имеет смысла. Присоединяясь к этой цепочке обещаний, каждый из них подразумевает намерение, что оно будет зависеть от успешного разрешения d1 и результата, передаваемого по цепочке. Если обещание в promise1 не получает значение выигрыша, а вместо этого получает значение err в своем обработчике ошибок, как возможно для следующего обещания в цепочке вызвать функцию успеха? Невозможно передать значимое значение следующему обещанию, потому что оно само не получило значения.

Я могу по-другому описать то, о чем я думаю: есть три человека, Джон, Джинджер и Боб. Джон владеет магазином виджетов. Джинджер заходит в свой магазин и запрашивает сумку виджетов разных цветов. У него их нет в наличии, поэтому он отправляет запрос своему дистрибьютору, чтобы они отправили его ему. В то же время, он дает Джинджер проверку дождя, заявляя, что он должен ей пакет виджетов. Боб узнает, что Джинджер получает виджеты и просит, чтобы он получил синий виджет, когда она закончит с ними. Она соглашается и дает ему записку о том, что она будет. Теперь дистрибьютор Джона не может найти какие-либо виджеты в своем ассортименте, а производитель их больше не производит, поэтому они информируют Джона, который, в свою очередь, сообщает Джинджер, что она не может получить виджеты. Как Боб может получить синий виджет от Джинджер, когда сам не получил?

Третий более реалистичный взгляд на этот вопрос у меня заключается в следующем. Скажем, у меня есть два значения, которые я хочу обновить в базе данных. Один зависит от идентификатора другого, но я не могу получить идентификатор, пока я не вставил его в базу данных и не получил результат. Кроме того, первая вставка зависит от запроса из базы данных. Обращение к базе данных возвращает обещания, которые я использую для объединения двух вызовов в последовательность.

var promise = db.query({parent_id: value});
promise.then(function(query_result) {
    var first_value = {
        parent_id: query_result[0].parent_id
    }
    var promise = db.put(first_value);
    promise.then(function(first_value_result) {
        var second_value = {
            reference_to_first_value_id: first_value_result.id
        }
        var promise = db.put(second_value);
        promise.then(function(second_value_result) {
            values_successfully_entered();
        }, function(err) { return err });
    }, function(err) { return err });
}, function(err) { return err });

Теперь, в этой ситуации, если db.query потерпел неудачу, он вызвал бы функцию err первого, затем Но затем он вызвал бы функцию успеха следующего обещания. Пока это обещание ожидает результатов первого значения, он вместо этого получил бы сообщение об ошибке из своей функции обработчика ошибок.

Итак, мой вопрос: зачем мне использовать функцию обработки ошибок, если мне нужно проверить ошибки в моей функции успеха?

Извините за длину этого. Я просто не знал, как объяснить это по-другому.

ОБНОВЛЕНИЕ и исправление

(Примечание. Я удалил ответ, который когда-то сделал на некоторые комментарии. Поэтому, если кто-то прокомментирует мой ответ, его комментарии могут показаться неконтролируемыми, если я их удалил. это, я стараюсь сделать это как можно короче.)

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

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

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

Нет. То, что вы описываете, это не цепочка, а просто присоединение всех обратных вызовов к d1. Тем не менее, если вы хотите связать что-то с then, результат для promise2 зависит от разрешения promise1 и от того, как обратные вызовы then обрабатывали его Глядя на вывод

Состояние документа:

Возвращает новый обещание результата обратного вызова (-ей).

Переменная .then метод обычно рассматривается в терминах Обещаний / Спецификации (или даже более строгих Обещаний / А {{}). Это означает, что оболочка обратного вызова возвращает обещания, которые будут ассимилированы, чтобы стать разрешением — так что вы можете просто promise2, тогда как promise2 опустить обработчик для распространения ошибки. Тем не менее, если ошибка

обработана , результирующий будет считаться фиксированным и будет выполнен с этим значением. Если вы этого не хотите, вам придется promise2 повторно повторить ошибку throw, как в предложении try-catch. В качестве альтернативы вы можете вернуть (чтобы быть) отклоненное обещание от обработчика. Не знаю, что такое отказ от Dojo, но: Как Боб может получить синий виджет от Джинджер, если сам не получил?

var d1 = d();

var promise1 = d1.promise.then(
    function(wins) { console.log('promise1 resolved'); return wins;},
    function(err) { console.log('promise1 rejected'); throw err;});
var promise2 = promise1.then(
    function(wins) { console.log('promise2 resolved'); return wins;},
    function(err) { console.log('promise2 rejected'); throw err;});
var promise3 = promise2.then(
    function(wins) { console.log('promise3 resolved'); return wins;},
    function(err) { console.log('promise3 rejected'); throw err;});
d1.reject(new Error());

Он не должен быть в состоянии. Если нет обработчиков ошибок, он просто воспримет сообщение (((от дистрибьютора) от Джона) от Джинджер), что не осталось никаких виджетов. Тем не менее, если Джинджер установит обработчик ошибок для этого случая, она все равно может выполнить свое обещание дать Бобу виджет, дав ему зеленый из своей лачуги, если у Джона или его дистрибьютора не осталось синих.

Чтобы перевести ваши обратные вызовы ошибок в метафер,

из обработчика было бы все равно, что сказать «если не осталось виджетов, просто отметьте, что их больше нет — это так же хорошо, как нужный виджет» ». return err В ситуации с базой данных, если db.query потерпел неудачу, он вызвал бы функцию err первого, а затем

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

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

var promise = db.query({parent_id: value});
promise.then(function(query_result) {
    var first_value = {
        parent_id: query_result[0].parent_id
    }
    var promise = db.put(first_value);
    return promise.then(function(first_value_result) {
        var second_value = {
            reference_to_first_value_id: first_value_result.id
        }
        var promise = db.put(second_value);
        return promise.then(function(second_value_result) {
            return values_successfully_entered();
        });
    });
});

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

db.query({parent_id: value}).then(function(query_result) {
    return db.put({
        parent_id: query_result[0].parent_id
    });
}).then(function(first_value_result) {
    return db.put({
        reference_to_first_value_id: first_value_result.id
    });
}.then(values_successfully_entered);

@ Jordan, во-первых, как отметили комментаторы, при использовании отложенной библиотеки ваш первый пример определенно дает ожидаемый результат :

promise1 rejected
promise2 rejected
promise3 rejected

Во-вторых, даже если бы он выдавал вывод, который вы предлагаете, это не повлияло бы на ход выполнения вашего второго фрагмента, который немного отличался бы, например:

promise.then(function(first_value) {
    console.log('promise1 resolved');
    var promise = db.put(first_value);
    promise.then(function (second_value) {
         console.log('promise2 resolved');
         var promise = db.put(second_value);
         promise.then(
             function (wins) { console.log('promise3 resolved'); },
             function (err) { console.log('promise3 rejected'); return err; });
    }, function (err) { console.log('promise2 rejected'); return err;});
}, function (err) { console.log('promise1 rejected'); return err});

, и в случае отклонения первого обещания будет просто вывод:

promise1 rejected

Однако ( переход к наиболее интересной части ), хотя отложенная библиотека определенно возвращает 3 x rejected, большинство других библиотек обещаний вернет 1 x rejected, 2 x resolved (что приводит к предположению, что вы получили эти результаты используя вместо этого другую библиотеку обещаний).

Что еще более запутанно, эти другие библиотеки более корректны в своем поведении. Позволь мне объяснить.

В мире синхронизации аналогом «отклонения обещания» является throw. Таким образом, с точки зрения семантики, async deferred.reject(new Error()) в синхронизации равно throw new Error(). В вашем примере вы не генерируете ошибки в своих обратных вызовах синхронизации, вы просто возвращаете их, поэтому вы переключаетесь на поток успеха, причем ошибка является значением успеха. Чтобы убедиться, что отклонение передается дальше, вам необходимо повторно выдать свои ошибки:

function (err) { console.log('promise1 rejected'); throw err; });

Итак, теперь вопрос: почему отложенная библиотека приняла возвращенную ошибку как отклонение?

Причиной этого является то, что отказ в отсрочке работает немного иначе. В отложенной lib правило: обещание отклоняется, когда оно разрешается с экземпляром ошибки , поэтому даже если вы выполните deferred.resolve(new Error()), оно будет действовать как deferred.reject(new Error()), и если вы попытаетесь выполнить deferred.reject(notAnError) он выдаст исключение, сказав, что обещание может быть отклонено только в случае ошибки. Это объясняет, почему ошибка, возвращенная из обратного вызова then, отклоняет обещание.

За отсроченной логикой есть некоторые веские аргументы, но они все равно не соответствуют тому, как throw работает в JavaScript, и из-за этого это поведение планируется изменить с версией v0.7 от отложенного.

Краткое резюме:

Чтобы избежать путаницы и неожиданных результатов, просто следуйте правилам хорошей практики:

  1. Всегда отклоняйте свои обещания с ошибками (следуйте правилам мира синхронизации, где выбрасывание значения, которое не является ошибкой, считается плохая практика).
  2. Отклонить от обратных вызовов синхронизации путем выброса ошибок (их возврат не гарантирует отклонение).

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

Использование может переносить ошибки на каждом уровне Обещания. Я связал ошибки в TraceError :

class TraceError extends Error {
  constructor(message, ...causes) {
    super(message);

    const stack = Object.getOwnPropertyDescriptor(this, 'stack');

    Object.defineProperty(this, 'stack', {
      get: () => {
        const stacktrace = stack.get.call(this);
        let causeStacktrace = '';

        for (const cause of causes) {
          if (cause.sourceStack) { // trigger lookup
            causeStacktrace  = `n${cause.sourceStack}`;
          } else if (cause instanceof Error) {
            causeStacktrace  = `n${cause.stack}`;
          } else {
            try {
              const json = JSON.stringify(cause, null, 2);
              causeStacktrace  = `n${json.split('n').join('n    ')}`;
            } catch (e) {
              causeStacktrace  = `n${cause}`;
              // ignore
            }
          }
        }

        causeStacktrace = causeStacktrace.split('n').join('n    ');

        return stacktrace   causeStacktrace;
      }
    });

    // access first error
    Object.defineProperty(this, 'cause', {value: () => causes[0], enumerable: false, writable: false});

    // untested; access cause stack with error.causes()
    Object.defineProperty(this, 'causes', {value: () => causes, enumerable: false, writable: false});
  }
}

Использование

throw new TraceError('Could not set status', srcError, ...otherErrors);

Вывод

javascript - обещаний в цепочке, не переходящих на отклонение

раньше

TraceError#cause - first error
TraceError#causes - list of chained errors