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

May 22 2014

Я писал код, который выглядит примерно так:

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

Ответы

380 BenjaminGruenbaum May 22 2014 at 17:07

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

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

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

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

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

Цитата Есаилия:

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

142 Bergi Aug 29 2014 at 20:28

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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