Что такое явное обещание строительства антипаттерна и как его избежать?

Я писал код, который делает что-то похожее на:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }

Кто-то сказал мне, что это называется "отложенным антипаттерном" или "Promiseконструктор антипаттерна"соответственно, что плохого в этом коде и почему он называется антипаттерном?

3 ответа

Решение

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

Обещания могут соединиться с .then и вы можете вернуть обещание напрямую. Ваш код в getStuffDone можно переписать как:

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}

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

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

Цитируя Esailija:

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

Что с этим не так?

Но шаблон работает!

Повезло тебе. К сожалению, это, вероятно, не так, как вы, вероятно, забыли какой-то крайний случай. В более чем половине случаев, которые я видел, автор забыл позаботиться об обработчике ошибок:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})

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

То же самое происходит в случае, если ваш код обратного вызова вызывает ошибку - например, когда result не имеет property и исключение брошено. Это было бы необработанным и оставило бы новое обещание нерешенным.

Напротив, используя .then() автоматически выполняет оба этих сценария и отклоняет новое обещание при возникновении ошибки:

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })

Отложенный антипаттерн не только громоздок, но и подвержен ошибкам. С помощью .then() для цепочки гораздо безопаснее.

Но я справился со всем!

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

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

Как мне этого избежать?

Поэтому всякий раз, когда вы обнаруживаете, что создаете вручную Promise или же Deferred и уже существующие обещания участвуют, сначала проверьте API библиотеки. Отложенный антипаттерн часто применяется людьми, которые видят обещания [только] как образец наблюдателя - но обещания - это больше, чем обратные вызовы: они должны быть составными. Каждая приличная библиотека имеет множество простых в использовании функций для составления обещаний любым мыслимым образом, заботясь обо всех вещах низкого уровня, с которыми вы не хотите иметь дело.

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

Теперь, 7 лет спустя, на этот вопрос есть более простой ответ:

Как избежать явного антипаттерна конструктора?

Используйте s, затем каждое обещание!

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

      function promised() {
  return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
      getAnotherPromise(result).then(function(result2) {
        resolve(result2);
      });
    });
  });
}

просто включите свою функцию и используйте ключевое слово, чтобы остановить выполнение функции, пока обещание не разрешится:

      async function promised() {
   const result =  await getOtherPromise();
   const result2 = await getAnotherPromise(result);
   return result2;
}

Это дает различные преимущества:

  • Вызов async функция всегда возвращает обещание, которое разрешается с возвращенным значением и отклоняет, если ошибка возникает внутри асинхронной функции
  • Если ed Promise отклоняет, ошибка возникает внутри асинхронной функции, поэтому вы можете просто try { ... } catch(error) { ... } это вроде синхронных ошибок.
  • Вы можете внутри циклов и ветвей if, делая большую часть логики цепочки промисов тривиальной.
  • Хотя асинхронные функции ведут себя в основном как цепочки обещаний, их легче читать (и легче рассуждать)

Как мне перезвонить?

Если обратный вызов выполняет обратный вызов только один раз, а API, который вы вызываете, уже не предоставляет Promise (большинство из них это делают!), Это единственная причина использовать конструктор Promise:

       // Create a wrapper around the "old" function taking a callback, passing the 'resolve' function as callback
 const delay = time => new Promise((resolve, reject) =>
   setTimeout(resolve, time)
 ); 

 await delay(1000);

Если выполнение останавливается, возвращает ли вызов результат напрямую?

Нет. Если вы вызываете асинхронную функцию, всегда возвращается обещание. Затем вы можете это Promise также внутри асинхронной функции. Вы не можете дождаться результата внутри синхронной функции (вам придется вызвать .then и прикрепите обратный вызов).

Концептуально синхронный functionвсегда выполняются до завершения, в то время как async functionработают синхронно, пока не достигнут await.

Другие вопросы по тегам