Рубрики
Без рубрики

Как использовать JavaScript Proxies для веселья и прибыли

Прокси – недавняя и мощная особенность языка JavaScript. С ними вы можете даже перехватить атрибуты или вызовы методов, которые не существуют. Давайте копаемся в свои возможности

Автор оригинала: 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 Элегантный и читаемый Отказ Я еще не сделал никаких ориентиров, но если вы планируете использовать их в производстве, я бы сначала проделал некоторое тестирование производительности.

Тем не менее, нет проблем с использованием их о разработке, как мониторинг асинковых функций для отладки!