Автор оригинала: Alberto Gimeno Brieba.
Существует очень недавняя новая особенность языка JavaScript, который до сих пор не используется широко распространенным: JavaScript Proxies Отказ
С JavaScript Proxies вы можете обернуть существующий объект и Перехватить любой доступ к своим атрибутам или методам Отказ Даже если они не существуют! 💥.
Вы можете перехватить вызовы методов, которые не существуют
Hello World Proxy
Давайте начнем с основы. Пример «Hello World» может быть:
const wrap = obj => { return new Proxy(obj, { get(target, propKey) { console.log(`Reading property "${propKey}"`) return target[propKey] } }) } const object = { message: 'hello world' } const wrapped = wrap(object) console.log(wrapped.message)
Какие выходы:
Reading property "message" hello world
В этом примере мы просто делаем что-то, прежде чем доступ к свойству/методу. Но тогда мы вернем первоначальную собственность или метод.
Вы также можете перехватить изменения в свойства, реализуя Установить
обработчик.
Это может быть полезно для проверки атрибутов или таких вещей. Но я думаю, что эта особенность гораздо более многообещающая. Я надеюсь, что новые рамки возникнут, что будет использовать прокси для своей основной функциональности. Я думал об этом, и это некоторые идеи:
🚀 SDK для API с 20 линиями кода
Как я уже сказал, ты можешь перехватить Способ вызовов для методов, которые … даже не существует. Когда кто-то вызывает метод на прокси-объекте получить
Обработчик будет вызван, а затем вы можете вернуть динамически сгенерированную функцию. Вам не нужно касаться прокси-объекта, если вам не нужно.
Имея в виду этой идеи, вы можете проанализировать этот метод, вызываемый и динамически реализовать его функциональность во время выполнения! Например, мы могли бы иметь прокси, который при вызове с API.GetUsers ()
Это может сделать Получить/пользователи
в API. С этой конвенцией мы можем пойти дальше и API.PostiTems ({name: 'Имя предмета'})
позвонил бы Пост/предметы
с первым параметром в качестве корпуса запроса.
Давайте посмотрим полный выполнение:
const { METHODS } = require('http') const api = new Proxy({}, { get(target, propKey) { const method = METHODS.find(method => propKey.startsWith(method.toLowerCase())) if (!method) return const path = '/' + propKey .substring(method.length) .replace(/([a-z])([A-Z])/g, '$1/$2') .replace(/\$/g, '/$/') .toLowerCase() return (...args) => { const finalPath = path.replace(/\$/g, () => args.shift()) const queryOrBody = args.shift() || {} // You could use fetch here // return fetch(finalPath, { method, body: queryOrBody }) console.log(method, finalPath, queryOrBody) } } } ) // GET / api.get() // GET /users api.getUsers() // GET /users/1234/likes api.getUsers$Likes('1234') // GET /users/1234/likes?page=2 api.getUsers$Likes('1234', { page: 2 }) // POST /items with body api.postItems({ name: 'Item name' }) // api.foobar is not a function api.foobar()
Здесь проксированный объект просто {}
Потому что все методы динамически реализованы. Нам не нужно обернуть Функциональный объект.
Вы увидите, что некоторые методы имеют $
Это заполнитель для свернутых параметров.
Если вам это не нравится, это может быть реализовано по-другому. 🙂.
Боковая заметка : Эти примеры могут быть оптимизированы. Вы можете кэшировать динамически сгенерированную функцию в хеш-объекте вместо того, чтобы возвращать новую функцию каждый раз. Но для ясности я оставил это так для примеров.
📦 Запросы данных структур данных с более читаемыми методами
Что, если у вас был массив людей, и вы могли бы сделать:
arr.findWhereNameEquals('Lily') arr.findWhereSkillsIncludes('javascript') arr.findWhereSkillsIsEmpty() arr.findWhereAgeIsGreaterThan(40)
Конечно, вы можете с прокси! Мы можем реализовать прокси, который включает массив, анализирует вызовы методов и запрашивает такие запросы.
Я внедрил несколько возможностей здесь:
const camelcase = require('camelcase') const prefix = 'findWhere' const assertions = { Equals: (object, value) => object === value, IsNull: (object, value) => object === null, IsUndefined: (object, value) => object === undefined, IsEmpty: (object, value) => object.length === 0, Includes: (object, value) => object.includes(value), IsLowerThan: (object, value) => object === value, IsGreaterThan: (object, value) => object === value } const assertionNames = Object.keys(assertions) const wrap = arr => { return new Proxy(arr, { get(target, propKey) { if (propKey in target) return target[propKey] const assertionName = assertionNames.find(assertion => propKey.endsWith(assertion)) if (propKey.startsWith(prefix)) { const field = camelcase( propKey.substring(prefix.length, propKey.length - assertionName.length) ) const assertion = assertions[assertionName] return value => { return target.find(item => assertion(item[field], value)) } } } }) } const arr = wrap([ { name: 'John', age: 23, skills: ['mongodb'] }, { name: 'Lily', age: 21, skills: ['redis'] }, { name: 'Iris', age: 43, skills: ['python', 'javascript'] } ]) console.log(arr.findWhereNameEquals('Lily')) // finds Lily console.log(arr.findWhereSkillsIncludes('javascript')) // finds Iris
Было бы супер похожим на написание библиотеки утверждения, как ожидать используя прокси.
Другая идея будет создать библиотеку для запроса баз данных с API, как это:
const id = await db.insertUserReturningId(userInfo) // Runs an INSERT INTO user ... RETURNING id
📊 Мониторинг асинхронных функций
Поскольку вы можете перехватить вызовы методов, если метод вызова возвращает обещание Вы также можете отследить, когда будет выполнен обещанный Отказ С этой идеей я сделал быстрый пример мониторинга асинковых методов объекта и печатать некоторые статистические данные в командной строке.
У вас есть услуга, как следующее, и с одним вызовом метода вы можете обернуть его:
const service = { callService() { return new Promise(resolve => setTimeout(resolve, Math.random() * 50 + 50)) } } const monitoredService = monitor(service) monitoredService.callService() // we want to monitor this
Это полный пример:
const logUpdate = require('log-update') const asciichart = require('asciichart') const chalk = require('chalk') const Measured = require('measured') const timer = new Measured.Timer() const history = new Array(120) history.fill(0) const monitor = obj => { return new Proxy(obj, { get(target, propKey) { const origMethod = target[propKey] if (!origMethod) return return (...args) => { const stopwatch = timer.start() const result = origMethod.apply(this, args) return result.then(out => { const n = stopwatch.end() history.shift() history.push(n) return out }) } } }) } const service = { callService() { return new Promise(resolve => setTimeout(resolve, Math.random() * 50 + 50)) } } const monitoredService = monitor(service) setInterval(() => { monitoredService.callService() .then(() => { const fields = ['min', 'max', 'sum', 'variance', 'mean', 'count', 'median'] const histogram = timer.toJSON().histogram const lines = [ '', ...fields.map(field => chalk.cyan(field) + ': ' + (histogram[field] || 0).toFixed(2)) ] logUpdate(asciichart.plot(history, { height: 10 }) + lines.join('\n')) }) .catch(err => console.error(err)) }, 100)
JavaScript Proxies Super мощный. ✨.
Они добавляют немного накладных расходов, но на оборотной стороне, имеющие способность динамически реализовывать методы во время выполнения по их имени, делает код Super Элегантный и читаемый Отказ Я еще не сделал никаких ориентиров, но если вы планируете использовать их в производстве, я бы сначала проделал некоторое тестирование производительности.
Тем не менее, нет проблем с использованием их о разработке, как мониторинг асинковых функций для отладки!