ФорумПрограммированиеJavaScript → @decorators

@decorators

  • Trej Gun

    Сообщения: 5299 Репутация: N Группа: в ухо

    Spritz 20 января 2016 г. 1:28

    Еще одна новая хуйня в es7 даже в бабеле работает с трудом, но ахуенна как аннотации в java

    итак разберем зачем это надо на реальном примере
    у нас есть гипотетическое API, допустим мы гребем посты форума
    выглядит как любое нормальное API

    
    const api = new API({accountSID, privateKey});
    api.getPosts({author: "TrejGun"})
        .then(console.log)
        .catch(console.error)
    

    и вот в какой-то момент нам понадобилось отключать это конкретное API по ключу из команд лайна
    например мы не хотим отправлять запросы в платное API когда гоняем тесты

    API=false node run test

    для этого пишем такой класс

    
    class MyAPI {
    
        client = null;
    
        constructor(data) {
            this.client = new API(data);
        }
    
        getPosts(data) {
            if (process.env.API === "true") {
                return this.client.getPosts(data);
            } else {
                return new Promise((resolve, reject) => {
                    reject(new Error(`process.env.API != true, method is mocked up!`));
                });
            }
        }
    }
    
    const api = new MyAPI({accountSID, privateKey});
    
    api.getPosts({author: "TrejGun"})
        .then(console.log)
        .catch(console.error);
    

    а что если у нас таких API 10 и в каждом по 10 методов
    не много ли ифов выйдет? вынесем иф в функцию обертку

    
    function wrapper(fn) {
        return function (...args) {
            if (process.env[this.constructor.key] === "true") {
                return fn.bind(this)(...args);
            } else {
                return new Promise((resolve, reject) => {
                    reject(new Error(`process.env.API != true, method is mocked up!`));
                });
            }
        };
    }
    
    class MyAPI {
    
        static key = "API";
    
        client = null;
    
        constructor(data) {
            this.client = new API(data);
        }
    
    }
    
    MyAPI.prototype.getPosts = wrapper(function (data) {
        return this.client.getPosts(data);
    });
    
    
    const api = new MyAPI({accountSID, privateKey});
    
    api.getPosts({author: "TrejGun"})
        .then(console.log)
        .catch(console.error);
    

    выглядит как говно если честно
    особенно там где CLASS.prototype
    а если враперов несколько, то код получается совсем грязный

    
    MyAPI.prototype.getPosts = wrapper1(wrapper2(wrapper3(function (data) {
        return this.client.getPosts(data);
    })));
    

    хуй вообще поймешь что происходит
    и тут нам на помощь приходят декораторы
    а точнее декорирующий декоратор

    декоратор это функция которая принимает в себя target, key, descriptor
    из которых в данном случаи нам нужен только descriptor
    descriptor - это объект который принимает на вход метод Object.defineProperty()
    наш декоратор изменяет его и возвращает обратно
    в самом общем случаи ничего не делающий декоратор выглядит вот так

    
    function decorate() {
        return (target, key, descriptor) => descriptor;
    }
    

    и работает примерно так

    
    Object.defineProperty(API, "getPosts", {
        enumerable: false,
        configurable: false,
        writable: false,
        value(data) {
            return this.client.getPosts(data);
        }
    });
    

    обернем во врапер

    
    Object.defineProperty(API, "getPosts", {
        configurable: true,
        enumerable: false,
        value: wrapper(function(data) {
            return this.client.getPosts(data);
        })
    });
    

    перенесем в декоратор

    
    function decorate(decorator) {
        return (target, key, descriptor) => ({
            configurable: true,
            enumerable: false,
            value: decorator(descriptor.value)
        });
    }
    

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

    
    
    process.env.API = "true";
    function decorate(decorator) {
        return (target, key, descriptor) => ({
            configurable: true,
            enumerable: false,
            value: decorator(descriptor.value)
        });
    }
    
    function makeError(key) {
        return new Error(`process.env.${key} != true, method is mocked up!`);
    }
    
    function callbackDecorator(fn) {
        return function (...args) {
            if (process.env[this.constructor.key] === "true") {
                fn.bind(this)(...args);
            } else {
                process.nextTick(() => {
                    args[args.length - 1](makeError(this.constructor.key)); // done
                });
            }
        };
    }
    
    function promiseDecorator(fn) {
        return function (...args) {
            if (process.env[this.constructor.key] === "true") {
                return fn.bind(this)(...args);
            } else {
                return new Promise((resolve, reject) => {
                    reject(makeError(this.constructor.key));
                });
            }
        };
    }
    
    class MyAPI {
    
        static key = "API";
    
        constructor() {
            // init some 3rd party API client
        }
    
        @decorate(callbackDecorator)
        callbackMethod(done) {
            setTimeout(() => {
                done(null, {result: "callback"});
            }, 100);
        }
    
        @decorate(promiseDecorator)
        promiseMethod() {
            return new Promise((resolve) => {
                resolve({result: "promise"});
            });
        }
    
    }
    
    const api = new MyAPI();
    
    api.callbackMethod((error, result) => {
        console.log(error, result);
    });
    
    api.promiseMethod()
        .then(console.log)
        .catch(console.error);
    
    

    получилось очень компактно, нету присвоения в прототип класса
    и декораторы можно применять по очереди не делая код менее читаемым

    
    @decorate(wrapper1)
    @decorate(wrapper2)
    method() {
        // do something
    }
    

    ребята как обычно о вас уже позаботились и вам не надо будет писать всю эту хуиту самостоятельно
    потому что тут уже есть несколько готовых декораторов в том числе и тот что в моем примере

  • kostyl

    Сообщения: 5198 Репутация: N Группа: Джедаи

    Spritz 20 января 2016 г. 4:51, спустя 3 часа 22 минуты 4 секунды

    +1

  • phpdude

    Сообщения: 26610 Репутация: N Группа: в ухо

    Spritz 20 января 2016 г. 6:21, спустя 1 час 30 минут 40 секунд

    декораторы появились в JavaScript охуеть можно. ну как там #php то, еще не помер от скуки что его все уже обогнали? :D

    Сапожник без сапог
  • Ivan.

    Сообщения: 494 Репутация: N Группа: Адекваты

    Spritz 20 января 2016 г. 8:45, спустя 2 часа 23 минуты 58 секунд

    Тоже в последнее время тянет на js кодить, в т.ч. серверную сторону. Жалко что сильно привык к django.

    Спустя 92 сек.

    А декораторов пиздец как нехватает в ангуляре. Например можнео бы было написать типа

    @onlyIfAuth - типа выполнять функцию только для авторизованных
    или перед контроллером @authRequired

  • Trej Gun

    Сообщения: 5299 Репутация: N Группа: в ухо

    Spritz 20 января 2016 г. 9:53, спустя 1 час 7 минут 24 секунды

    а что там в пхп из новинок? трейты?

  • phpdude

    Сообщения: 26610 Репутация: N Группа: в ухо

    Spritz 20 января 2016 г. 10:02, спустя 9 минут 33 секунды

    а что там в пхп из новинок? трейты?

    @CTAPbIu_MABP, геморейты

    Сапожник без сапог
  • Trej Gun

    Сообщения: 5299 Репутация: N Группа: в ухо

    Spritz 20 января 2016 г. 10:04, спустя 1 минуту 50 секунд

    неправда! это всегда было!!!

  • phpdude

    Сообщения: 26610 Репутация: N Группа: в ухо

    Spritz 20 января 2016 г. 10:09, спустя 4 минуты 37 секунд

    @CTAPbIu_MABP, ты прав, я ошибся судя по всему

    Сапожник без сапог
  • Crank

    Сообщения: 541 Репутация: N Группа: Джедаи

    Spritz 21 января 2016 г. 9:40, спустя 23 часа 31 минуту 43 секунды

    @CTAPbIu_MABP, оно вроде еще с 5.4, та еще новинка

Пожалуйста, авторизуйтесь, чтобы написать комментарий!