Еще одна новая хуйня в 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
}
ребята как обычно о вас уже позаботились и вам не надо будет писать всю эту хуиту самостоятельно
потому что тут уже есть несколько готовых декораторов в том числе и тот что в моем примере
Object.defineProperty()
The Object.defineProperty() method defines a new property directly on an object, or modifies an existing property on an object, and returns the object
developer.mozilla.org
jayphelps/core-decorators.js
core-decorators.js - Library of JavaScript decorators (aka ES2016/ES7 decorators) inspired by languages that come with built-ins like @override, @de
github.com