Spec-Zone.ru › Bluebird

Антипаттерны промисов

Эта страница содержит распространённые антипаттерны промисов, встречающиеся в дикой природе.

  • Антипаттерн явного создания
  • Антипаттерн .then(success, fail)

Антипаттерн явного создания

Это самый распространённый антипаттерн. Легко попасть в него, если вы не совсем понимаете промисы и считаете их улучшенными эмиттерами событий или утилитой для обратных вызовов. Иногда его также называют антипаттерном конструктора промисов. Давайте вспомним: промисы предназначены для того, чтобы асинхронный код сохранял большинство утраченных свойств синхронного кода, таких как плоская вложенность и один канал исключений. Этот паттерн также называют антипаттерном отложенных промисов.

В антипаттерне явного создания объекты промисов создаются без необходимости, усложняя код.

Первый пример – создание объекта отложенного промиса, когда у вас уже есть промис или thenable:

//Code copyright by Twisternha http://stackoverflow.com/a/19486699/995876 CC BY-SA 2.5
myApp.factory('Configurations', function (Restangular, MotorRestangular, $q) {
    var getConfigurations = function () {
        var deferred = $q.defer();

        MotorRestangular.all('Motors').getList().then(function (Motors) {
            //Group by Config
            var g = _.groupBy(Motors, 'configuration');
            //Map values
            var mapped = _.map(g, function (m) {
                return {
                    id: m[0].configuration,
                    configuration: m[0].configuration,
                    sizes: _.map(m, function (a) {
                        return a.sizeMm
                    })
                }
            });
            deferred.resolve(mapped);
        });
        return deferred.promise;
    };

    return {
        config: getConfigurations()
    }

});

Это избыточное обертывание также опасно; любые ошибки и отклонения будут поглощены и не будут переданы вызывающей функции.

Вместо использования антипаттерна отложенного промиса код должен просто вернуть имеющийся промис и распространять значения с помощью return:

myApp.factory('Configurations', function (Restangular, MotorRestangular, $q) {
    var getConfigurations = function () {
        //Just return the promise we already have!
        return MotorRestangular.all('Motors').getList().then(function (Motors) {
            //Group by Cofig
            var g = _.groupBy(Motors, 'configuration');
            //Return the mapped array as the value of this promise
            return _.map(g, function (m) {
                return {
                    id: m[0].configuration,
                    configuration: m[0].configuration,
                    sizes: _.map(m, function (a) {
                        return a.sizeMm
                    })
                }
            });
        });
    };

    return {
        config: getConfigurations()
    }

});

Код не только короче, но и, что важнее, при возникновении ошибки она будет корректно передана конечному потребителю.

Второй пример – создание функции, которая ничего не делает, кроме как вручную оборачивает API обратного вызова и делает это плохо:

function applicationFunction(arg1) {
    return new Promise(function(resolve, reject){ //Or Q.defer() in Q
      libraryFunction(arg1, function (err, value) {
        if (err) {
          reject(err);
        } else {
          resolve(value);
        }
    });
}

Это переизобретение велосипеда, поскольку любое обертывание API обратного вызова можно и следует выполнять непосредственно с помощью методов промисификации библиотеки промисов:

var applicationFunction = Promise.promisify(libraryFunction);

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

Когда следует использовать отложенный промис?

Ну, просто когда это необходимо.

Вам может понадобиться использовать объект отложенного промиса при обёртке API обратного вызова, не следующего стандартной конвенции. Например, setTimeout:

//setTimeout that returns a promise
function delay(ms) {
    var deferred = Promise.defer(); // warning, defer is deprecated, use the promise constructor
    setTimeout(function(){
        deferred.fulfill();
    }, ms);
    return deferred.promise;
}

Такие обёртки должны быть редкими. Если они распространены по причине, что библиотека промисов не может их обобщённо промисифицировать, вы должны открыть вопрос.

Если статическая промисификация (promisify и promisifyAll выполняются слишком медленно для использования во время выполнения), можно использовать Promise.fromCallback.

Также см. вопрос на StackOverflow для получения дополнительных примеров и обсуждения этой темы.

Антипаттерн .then(success, fail)

Почти гарантированный признак использования промисов как улучшенных обратных вызовов. Вместо doThat(function(err, success)) вы делаете doThat().then(success, err) и оправдываете себе, что, по крайней мере, код «менее связан» или что-то в этом роде.

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

var t0;
try {
    t0 = doThat();
}
catch(e) {

}
//deal with t0 here and waste the try-catch
var stuff = JSON.parse(t0);

Более вероятно, что вы написали бы это в синхронном мире так:

try {
    var stuff = JSON.parse(doThat());
}
catch(e) {

}

Поэтому, пожалуйста, напишите то же самое при использовании промисов:

doThat()
.then(function(v) {
    return JSON.parse(v);
})
.catch(function(e) {

});

.catch задан для встроенных промисов JavaScript и является «сахарной обёрткой» для .then(null, function(){}). Поскольку способ работы с ошибками в промисах почти является всей точкой (и единственной вещью, которую jQuery никогда не сделала правильно, даже если она использовала .pipe как .then), я действительно надеюсь, что используемая вами реализация предоставляет этот метод для повышения читаемости.

© 2013–2018 Petka Antonov
Licensed under the MIT License.
http://bluebirdjs.com/docs/anti-patterns.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API