Что такое явное обещание конструкции antipattern и как мне этого избежать?

517

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

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конструктор антипаттерн » соответственно. Что плохого в этом коде и почему он называется антипаттерном ?

Бенджамин Грюнбаум
источник
Могу ли я подтвердить, что для этого нужно (в контексте примера справа, а не слева) удалить getStuffDoneоболочку функции и просто использовать литерал Promise?
Дембински
1
или имеет catchблок в getStuffDoneобертке антипаттерн?
Дембински
1
По крайней мере , для родного Promiseпримера у вас также есть ненужные функции обертки для .thenи .catchобработчиков (т.е. это может быть просто .then(resolve).catch(reject).) Идеальным штормом анти-паттернов.
Ноа Фрейтас
6
@ NoahFreitas этот код написан таким образом для дидактических целей. Я написал этот вопрос и ответ, чтобы помочь людям, которые столкнулись с этой проблемой после прочтения большого количества кода, похожего на это :)
Бенджамин Грюнбаум
См. Также stackoverflow.com/questions/57661537/… чтобы узнать, как исключить не только явную конструкцию Promise, но и использование глобальной переменной.
Дэвид Спектор

Ответы:

357

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

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

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

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

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

Цитируя Esailija:

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

Бенджамин Грюнбаум
источник
@BenjaminGruenbaum: я уверен в том, что я использую отсрочку для этого, поэтому нет необходимости в новом вопросе. Я просто подумал, что это тот случай, который вы пропустили в своем ответе. То, что я делаю, больше похоже на противоположность агрегации, не так ли?
Мельвенс
1
@mhelvens Если вы вручную разделяете API без обратного вызова на API обещаний, который подходит для части «Преобразование API обратного вызова в обещания». Антипаттерн - это заверение обещания в другое обещание без веской причины. Вы не заключаете обещание для начала, поэтому оно здесь не применимо.
Бенджамин Грюнбаум
@BenjaminGruenbaum: О, я думаю, что сами отсрочки считались анти-паттерном, так как Bluebird их осуждает, а вы упомянули «преобразование API в обещания» (что также является случаем, когда не нужно начинать с обещания).
Мельвенс
@mhelvens Я думаю, что избыточный отложенный анти-паттерн будет более точным для того, что он на самом деле делает. Bluebird не .defer()одобряет API в новом (и безопасном для использования) конструкторе обещаний, но не (ни в коем случае) не отвергает идею создания обещаний :)
Бенджамин Грюнбаум
1
Спасибо @ Roamer-1888, твой отзыв помог мне наконец понять, в чём была моя проблема. Похоже, я создавал вложенные (невозвращенные) обещания, не осознавая этого.
гуроо
135

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

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

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

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 библиотеки . Отложенный антипаттерн часто применяется людьми, которые видят обещания [только] как образец наблюдателя - но обещания - это больше, чем обратные вызовы : они должны быть составными. Каждая приличная библиотека имеет множество простых в использовании функций для составления обещаний любым мыслимым образом, заботясь обо всех низкоуровневых вещах, с которыми вы не хотите иметь дело.

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

Берги
источник
Существуют ли другие примеры, кроме функции setTimeout, в которых можно использовать конструктор, но его не следует рассматривать как «обещание конструктора anitpattern»?
guest271314
1
@ guest271314: Все асинхронное, что не возвращает обещание. Хотя достаточно часто вы получаете лучшие результаты с преданными помощниками по обещанию библиотек. И убедитесь , что всегда promisify на самом низком уровне, так что это не « функция в том числеsetTimeout », но « функция setTimeoutсама по себе ».
Берги
«И всегда выполняйте обещания на самом низком уровне, так что это не« функция в том числе setTimeout», а« сама функция setTimeout»» Можете описать, связать различия между ними?
guest271314
@ guest271314 функция , которая включает в себя только вызов setTimeoutявно отличается от функции setTimeoutсамого , не так ли?
Берги
4
Я думаю, что один из важных уроков, который до сих пор не был четко сформулирован, состоит в том, что Promise и связанный с ним «then» представляют собой одну асинхронную операцию: начальная операция находится в конструкторе Promise, а конечная точка находится в « тогда 'функция. Поэтому, если у вас есть операция синхронизации, за которой следует асинхронная операция, поместите данные синхронизации в Promise. Если у вас есть асинхронная операция, за которой следует синхронизация, поместите информацию о синхронизации в 'then'. В первом случае верните оригинальное обещание. Во втором случае верните цепочку Promise / then (которая также является Promise).
Дэвид Спектор