Как создать Python-подобный декоратор в Javascript

1656648971 kak sozdat python podobnyj dekorator v javascript

Семь Галиция

1*nIO9zHIGk94uWLQ-XwRd9g

В этой статье я хочу показать вам, как я создал функцию декоратора, похожую на Python, в JavaScript, и в процессе показать по крайней мере один пример практического использования.

В конце я хочу продемонстрировать, как я использовал это для решения более глубокой проблемы, и, возможно, вдохновить кого-нибудь из вас сделать то же самое!

Начинаем

Давайте начнем с короткого повторения (или урока) о декораторах в Python. В Python есть функции, которые называются декораторами, придерживающимися этого синтаксиса, @decorator.

В коде за ними следовала бы другая функция, например:

Приведенный выше фрагмент кода использует синтаксическую сахарную версию использования декораторов в Python. Чтобы лучше понять, что здесь происходит, давайте уберем этот синтаксический сахар.

В этом втором фрагменте мы рассматриваем определение декоратора и то, как украшаем функцию без синтаксического сахара.

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

Используя эту технику обертывания нашей функции, мы можем выполнять задачи до и после выполнения нашей функции. Давайте рассмотрим пример использования, который прольет чуть больше света на то, как это работает.

Создание нашего декоратора

Мы собираемся создать декоратор времени, который сообщит нам, сколько времени нужно для выполнения функции.

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

Этот простой вариант использования является лишь верхушкой айсберга, когда речь идет о декораторах. Прежде чем мы подробнее рассмотрим случаи использования, я хочу показать, как легко достичь того же результата в JavaScript.

То, что вы видите выше, это тот самый декоратор синхронизации, который мы создали на Python, но написан на Javascript. Я хочу осветить несколько небольших изменений синтаксиса, отличающихся от версии Python.

Во-первых, в нашей функции декоратора мы используем анонимные функции JavaScript. Использование анонимной функции позволяет нам вернуть его во время определения.

Во-вторых, мы также использовали анонимную функцию в синтаксисе выражения функции в строке 10. Мы передаем анонимную функцию в качестве аргумента к timing вместо использования именуемой функции.

Это незначительные синтаксические отличия в языке, и, надеюсь, они не смутят вас, если вы с ними не знакомы.

Ныряние глубже

Теперь, когда мы рассмотрели простой пример, я хочу немного усугубить более полезный случай. В процессе мы собираемся пошагово решить проблему, с которой я столкнулся, когда решил искать это решение.

Это проблема из двух частей, поэтому давайте посмотрим первую часть: ошибочные сетевые вызовы.

1*1PHgY3g8dvTa-UtKJ6LUlg
Страница внутренней ошибки сервера GitHub 500

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

Так получилось, что можно! В следующем фрагменте мы создадим декоратор Javascript, который будет использовать для этого библиотеку npm под названием retry.

*Кстати, вполне возможно написать код повтора самостоятельно. Я решил использовать библиотеку, чтобы сделать вещи немного проще, поскольку это не предмет этой статьи.

Ладно, это было многое для изучения! Я пытался комментировать части, чтобы помочь объяснить, что происходит, но давайте разберем это шаг за шагом.

Во-первых, нужна библиотека повторных попыток, которую можно найти здесь, и мы продолжим создание нашей функции декоратора.

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

В настройках операции вы можете увидеть, что я установил {retries: 2}. Параметры можно передать в конструктор операций как объект, содержащий параметр конфигурации, который необходимо изменить. Настройка retries как можно указать максимальное количество попыток

Библиотека повторных попыток имеет немало настроек, которые можно использовать, чтобы настроить работу ваших повторных попыток, но я не хочу, чтобы меня отслеживали. Просмотрите библиотеку npm, чтобы узнать больше!

Далее мы настраиваем нашу анонимную функцию, которая будет обернуть нашу оригинальную функцию. Возможно, вы были немного смущены, когда посмотрели на строку 9, const args = arguments;. Я знаю, что меня смутило, когда я впервые это увидел, поэтому позвольте мне объяснить.

В основном, поскольку мы звоним wrapped далее внутри функции в нижней области видимости, нам нужно получить аргументы из нее сейчас, чтобы мы могли получить к ним доступ позже. Ключевое слово arguments захватывает доводы из текущей области функции как массив. Сохраняем ток arguments в args позволяя нам использовать его позже.

В следующей части все начинает становиться немного запутанным. Я использую сетевую библиотеку, которая возвращает обещания, и поэтому нам нужно перехватить обещания внутри декоратора. Перехват позволяет нам проверить, успешно или неудачно, а затем что-то с этим поделать.

Ниже приведена только внутренняя часть функции обертывания декоратора.

Чтобы перехватить ответ от сетевого вызова, мы заворачиваем нашу операцию (содержащую нашу функцию, которую мы хотим повторить) в другое обещание. Для тех, кто раньше использовал обещания, я знаю, что это может показаться странным, но это необходимо, чтобы убедиться, что мы обработаем результат этого вызова здесь. Это станет еще более очевидным, когда я покажу свой последний пример, поэтому следите за обновлениями!

Двигаясь дальше, теперь, когда мы имеем завернутую или оригинальную функцию и операцию повторяем внутри обещания, мы можем фактически вызвать wrapped. Когда мы называем это, мы должны убедиться, что используем apply(context, args...) чтобы можно было использовать переданные у него оригинальные параметры.

Получив ответ от сетевого вызова, мы обрабатываем случаи успеха и ошибки. Случай успеха здесь достаточно скучный: в общем, если мы получаем успешный ответ, мы решаем внешнее обещание с результатом.

Случай ошибки гораздо интереснее! Если запрос не удается, мы хотим попробовать еще раз, не правда ли? Этот фрагмент, if (operation.retry(err)) { return; } любопытно, потому что это сердце библиотеки повторов. По сути, мы стараемся снова вызвать функцию. При этом мы передаем ошибку в функцию retry.

Операция содержит внутренний массив ошибок и вызов повторной попытки с ошибкой переносит эту ошибку в массив, а затем снова вызывает операцию. Вот почему мы должны были вызвать нашу оригинальную функцию внутри operation.attempt().

Сначала я был немного смущен return; утверждение в скобках. Что я узнал, играя с этим, это то, что после звонка retry() функция должна вернуться так, чтобы operation может быть выполнено снова. В противном случае без возвращения он застрял в исполнении и потерпел неудачу из-за нерешенного обещания.

Последним интересным фрагментом кода выше является то, что мы не можем сделать еще одну попытку — то есть, мы только что выполнили последнюю попытку. В этом случае оператор if завершится неудачей, когда вы попытаетесь вызвать retry() и мы обходим этот блок в последний раздел, где мы reject(err). Это очень важно, поскольку если вы не включите эту последнюю обработку ошибок, внешнее обещание никогда не решится.

Последняя часть головоломки

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

Краткая предыстория: я работаю с Spotify API, и их маркеры аутентификации действуют всего 60 минут. Спустя короткий срок службы мне пришлось часто обновлять при разработке. Осознавая, что это может быть настоящей проблемой для пользователей, я вспомнил, как мы решали эту проблему во время стажировки, которую я проходил.

Мой предыдущий опыт с подобной ситуацией (в Swift на iOS) заставил меня написать этот последний кусок кода здесь. Я испытал это, и оно отлично работает! Пользователь, вероятно, никогда даже не узнает, что срок действия его маркера истек, как это должно быть, я могу добавить.

В приведенной выше функции есть много путей разветвления кода, и большинство из них не ново. Я сделал все, чтобы объяснить всю логику в комментариях. Я использую этот код и тестировал его много раз, и маркер обновляется и ответ возвращается, как ожидалось!

Ниже приведен краткий пример того, как я использую эту функцию после ее написания.

Мне очень понравилось писать эту статью, и я хотел бы услышать отзывы всех, кто чувствует, что до этого. Вышеприведенный код, вероятно, можно еще определенным образом оптимизировать, но это то, что я сейчас использую, и оно работает 🙂

Надеюсь, это помогло хотя бы одному человеку решить подобную проблему. Если да, я хотел бы услышать, как вы это использовали!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *