2013-05-04 2 views
26

У меня возникли проблемы с пониманием того, почему отклонения не передаются через цепочку обещаний, и я надеюсь, что кто-то сможет помочь мне понять, почему. Для меня привязка функциональности к цепочке обещаний подразумевает намерение, что я в зависимости от оригинального обещания выполнить. Трудно объяснить, поэтому позвольте мне сначала показать пример кода моей проблемы. (Примечание:. Этот пример использует узел и модуль отложенного узла я проверил это с Dojo 1.8.3 и имел те же самые результаты)Связанные обещания, не проходящие при отказе

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 и результата, передаваемого по цепочке. Если обещание в обещании1 не получает значения выигрышей, но вместо этого получает значение err в обработчике ошибок, как это возможно, чтобы следующее обещание в цепочке вызывало функцию успеха? Невозможно передать значимую ценность следующему обещанию, потому что оно не получило самого значения.

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

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

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

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

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

UPDATE и коррекция

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

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

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

+1

Какие версии вы используете? Это отображает 'отклонил' 3 раза для меня на 0.10.0 и отложил 0.6.3. – loganfsmyth

+0

Он работает и для меня на узле 0.8.3, и отложил 0.6.3 https://gist.github.com/Stuk/694b2377057453aa6946 –

ответ

25

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

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

Документы состояние:

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

.then метод обычно смотрел на в терминах Promises/A specification (или даже более строгим Promsises/A+ one). Это означает, что обратные вызовы возвращают обещания, которые будут ассимилированы, чтобы стать разрешением 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()); 

Как Боб смог получить синий виджет с имбирем, когда не получил себя?

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

Чтобы перевести обратные вызовы с ошибкой в ​​метафору, return err от обработчика просто походил бы на высказывание «если нет никаких виджетов, просто дайте ему примечание о том, что их нет - это так же хорошо, как и нужный виджет» ,

В ситуации с базой данных, если db.запрос не срабатывал, он вызывал бы функцию ошибки первой, а затем

... что означало бы, что ошибка обрабатывается там. Если вы этого не сделаете, просто опустите обратный вызов ошибки. Кстати, ваши обратные вызовы не дают 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); 
+3

При использовании angularJS с $ q ключевое слово throw должно быть заменено на $ q.reject (ERR). – Toilal

+3

Чтобы очистить примечание @ Toilal, * предпочтительная * замена 'throw', является' return $ q.reject (err) '. «бросить», я считаю, все еще работает; он намного медленнее. – Malvolio

1

@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. Таким образом, семантически, асинхронный 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. Отклонение от обратных вызовов синхронизации на сбрасывание ошибок (возврат их не гарантирует отклонения).

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

0

Использование может обернуть ошибки на каждом уровне обещания. Я прикован ошибки в 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); 

Выходные

Функции

TraceError#cause - first error 
TraceError#causes - list of chained errors 
Смежные вопросы