如何在Javascript中创建类似Python的装饰器

by Sam Galizia

由山姆·加利齐亚(Sam Galizia)

如何在Javascript中创建类似Python的装饰器 (How to create a Python-like Decorator in Javascript)

In this article, I want to show you how I created a Python-like decorator function in JavaScript and, in the process, show at least one practical use case.

在本文中,我想向您展示如何在JavaScript中创建类似Python的装饰器函数,并在此过程中至少展示一个实际用例。

At the end, I want to demonstrate how I used this to solve a more in-depth problem and maybe inspire some of you to do the same!

最后,我想展示一下我是如何使用它来解决更深入的问题的,也许可以激发你们中的一些人做同样的事情!

入门 (Getting started)

Let’s start with a quick refresher (or lesson) on decorators in Python. In Python, there are functions called decorators that follow this syntax, @decorator.

让我们从Python的装饰器快速入门(或课程)开始。 在Python中,遵循此语法的称为装饰器的函数@decorator

In code, they would be followed by another function like so:

在代码中,它们后面将是另一个函数,如下所示:

The above code snippet is using the syntactic sugar version of using decorators in Python. To understand better what is going on here, let’s remove that syntactic sugar.

上面的代码段使用的是在Python中使用装饰器的语法糖版本。 为了更好地了解此处发生的情况,让我们删除该语法糖。

In this second snippet, we’re looking at the definition of the decorator and how we decorate a function without the syntactic sugar.

在第二个片段中,我们将研究装饰器的定义以及如何在不使用语法糖的情况下装饰函数。

One of the most important points to take away from this is that the decorator is simply wrapping our original function inside a wrapper function and calling it.

最重要的一点是,装饰器只需将我们的原始函数包装在包装函数中并调用它即可。

By using this technique of wrapping our function, we are able to perform tasks before and after our function runs. Let’s take a look at a use case that will shed some more light on how this all works.

通过使用这种包装函数的技术,我们能够在函数运行之前和之后执行任务。 让我们看一下一个用例,该用例将为所有这些工作原理提供更多的启示。

创建我们的装饰器 (Creating our decorator)

We are going to create a timing decorator that will tell us how long it takes a function to run.

我们将创建一个定时装饰器,该装饰器将告诉我们函数运行需要多长时间。

In the timing decorator, we start by noting the current time and saving it as start. We then execute the original function, and after that has finished, mark the current time as end. Finally we return the difference between the start and end times, essentially giving us the time it took to run the function.

在定时装饰器中,我们首先记录当前时间并将其保存为开始时间。 然后,我们执行原始功能,然后完成该操作,将当前时间标记为结束。 最后,我们返回开始时间和结束时间之间的差,从本质上给了我们运行函数所花费的时间。

This simple use case is just the tip of the iceberg when it comes to decorators. Before we go more in depth with use cases, I want to show how easy it is to achieve the same result in JavaScript.

当涉及到装饰器时,这个简单的用例只是冰山一角。 在深入探讨用例之前,我想展示一下在JavaScript中实现相同结果的难易程度。

What you see above is the same timing decorator that we made in Python, but written in Javascript. I want to highlight a few of the small syntax changes that are different form the Python version.

上面您看到的是与我们在Python中制作的但使用Javascript编写的相同的定时装饰器。 我想强调一些与Python版本不同的小语法更改。

First, inside our decorator function we make use of anonymous functions in JavaScript. Using the anonymous function also allows us to return it while defining it.

首先,在装饰器函数内部,我们使用JavaScript中的匿名函数。 使用匿名函数还允许我们在定义它时返回它。

Second, we also used an anonymous function in the function expression syntax on line 10. We pass an anonymous function as the argument to timing rather than using a named function.

其次,我们还在第10行的函数表达式语法中使用了匿名函数。我们将匿名函数作为timing的参数传递,而不是使用命名函数。

These are minor syntax differences in the language and hopefully won’t trip you up too much if you aren’t familiar with them.

这些是该语言在语法上的细微差别,如果您不熟悉它们,希望不会让您感到过多。

潜水更深 (Diving deeper)

Now that we have looked at a simple example, I want to go into some depth on a more useful case. In the process we are going to step through the problem I ran into when I decided to pursue this solution.

现在,我们看了一个简单的示例,我想深入探讨一个更有用的案例。 在此过程中,我们将逐步解决我决定采用此解决方案时遇到的问题。

This is a two part problem so let’s take a look at the first part: failing network calls.

这是一个两部分的问题,因此让我们看一下第一部分:网络呼叫失败。

When working with APIs, you will undoubtedly find yourself dealing with network calls that fail for any number of reasons. Wouldn’t it be nice if we could have those network calls retry themselves?

使用API​​时,毫无疑问,您会发现自己因各种原因而失败的网络调用。 如果我们可以让这些网络调用自己重试,那不是很好吗?

Well it just so happens that you can! In the following snippet, we will create a decorator in Javascript that will use an npm library called retry to do just that.

好吧,恰好您可以! 在以下代码段中,我们将使用Javascript创建一个装饰器,该装饰器将使用一个名为retry的npm库来做到这一点。

*On a side note, it is totally possible to write the retry code yourself. I chose to use a library to make things a bit simpler, since it is not the focus of this article.

*另外,完全可以自己编写重试代码。 我选择使用一个库来简化事情,因为这不是本文的重点。

Okay that was a lot to take in! I tried to comment portions to help explain what is going on, but let’s break it down step by step.

好的,要接受很多东西! 我试图评论部分内容以帮助解释正在发生的事情,但让我们将其逐步分解。

First off, we require the retry library, which can be found here, and proceed with creating our decorator function.

首先,我们需要重试库(可以在此处找到),然后继续创建装饰器函数。

Inside our decorator, the first thing we want to do is create an operation. Operations are part of the retry library and denote that we have something we want to do that may require more than one attempt.

在装饰器内部,我们要做的第一件事是创建一个操作。 操作是重试库的一部分,它表示我们要执行的操作可能需要多次尝试。

In the setup of the operation you can see I have set {retries: 2}. Options can be passed into the operation constructor as an object containing the config option you want to change. Setting retries is how you can specify the maximum number of attempts.

在操作的设置中,您可以看到我已经设置了{retries: 2} 。 选项可以作为包含要更改的config选项的对象传递到操作构造函数中。 设置retries是您可以指定最大尝试次数的方法。

The retry library has quite a bit of configuration that you can leverage to customize how your retries function, but I don’t want to get side tracked by it. Check out the npm library to learn more!

重试库具有很多配置,您可以利用这些配置来自定义重试功能的方式,但是我不想被它跟踪。 查看npm库以了解更多信息!

Next, we setup our anonymous function that will wrap our original function. You may have been a bit confused when you took a look at line 9, const args = arguments;. I know I was confused by this when I first saw it, so let me explain.

接下来,我们设置匿名函数,该函数将包装原始函数。 当您看一下第9行时,您可能会有些困惑, const args = arguments; 。 我知道当我第一次看到它时对此感到困惑,所以让我解释一下。

Basically, since we are calling wrapped further inside the function in a lower scope, we need to grab the arguments from it now so that we can access them later. The keyword arguments grabs the arguments from the current function scope as an array. We store the current arguments in args allowing us to use it later.

基本上,由于我们在较低范围内的函数内部进一步调用了wrapped ,因此我们现在需要从函数中获取参数,以便以后可以访问它们。 关键字arguments从当前函数作用域中获取参数作为数组。 我们将当前arguments存储在args以便以后使用。

The next part is where things start to get a little confusing. I am using a networking library that returns promises, and because of this, we need to intercept the promise inside the decorator. Intercepting it allows us to check whether it succeeded or failed and then do something about it.

下一部分是开始变得有些混乱的地方。 我正在使用一个返回承诺的网络库,因此,我们需要在装饰器内部拦截该承诺。 拦截它可以让我们检查它是否成功,然后对其进行处理。

Below is just the internal part of our decorator wrapping function.

下面只是装饰包装函数的内部部分。

In order to intercept the response from the networking call, we wrap our operation (which contains our function we want to retry) inside of another promise. To those of you who have used promises before, I know this might be strange, but it is needed to make sure that we handle the result of that call here. It will be even more apparent why when I show my final example, so stay tuned!

为了拦截来自网络调用的响应,我们将操作(包含我们要重试的功能)包装在另一个promise中。 对于以前使用过Promise的人来说,我知道这可能很奇怪,但是需要确保我们在此处处理该调用的结果。 当我展示我的最后一个例子时,为什么还要继续关注,这将更加显而易见!

Moving on, now that we have wrapped or original function and the operation retry inside of the promise, we can actually call wrapped. When we call it, we need to make sure to use apply(context, args...) so that our original parameters passed into it can be used.

继续,现在我们已经包装了或原始的函数,并且操作在promise内部重试,我们实际上可以调用wrapped 。 调用它时,我们需要确保使用apply(context, args...)以便可以使用传递给它的原始参数。

Once we get a response from our network call, we handle the success and error case. The success case is pretty boring here: basically if we get a successful response we resolve the outer promise with the result.

一旦我们从网络呼叫中获得响应,就可以处理成功和错误的情况。 成功案例在这里很无聊:基本上,如果我们获得成功的响应,我们就会用结果解决外部承诺。

The error case is much more interesting! If the request fails, we want to try again right? This snippet, if (operation.retry(err)) { return; } is interesting because this is the heart of the retry library. We are basically attempting to call the function again. In doing so, we pass the current error into the retry function.

错误案例更加有趣! 如果请求失败,我们想再试一次吗? if (operation.retry(err)) { return; } if (operation.retry(err)) { return; }很有趣,因为这是重试库的核心。 我们基本上是在尝试再次调用该函数。 这样,我们将当前错误传递给重试函数。

The operation has an internal array of errors, and calling retry with an error pushes that error onto the array and then calls the operation again. This is why we had to call our original function inside of operation.attempt().

该操作有一个内部错误数组,使用错误调用重试会将错误推送到该数组上,然后再次调用该操作。 这就是为什么我们必须在operation.attempt()内部调用原始函数的原因。

I was a little bit confused at first by the return; statement inside of the braces. What I learned by playing around with this, though, is that after calling retry() the function has to return so that operation can be executed again. Otherwise, without the return it got stuck in execution and failed with an unresolved promise.

一开始,我有点困惑return; 大括号内的语句。 不过,我从中进行的学习是,在调用retry()该函数必须返回,以便可以再次执行该operation 。 否则,如果没有回报,它将陷入执行中,并因未解决的承诺而失败。

The final interesting piece in the above code is when we can’t make another attempt — that is, we just executed our final attempt. In that case, the if statement is going to fail when you try and call retry() , and we bypass that block to the final section where we reject(err). This is very important, because if you don’t include this final error handling, the outer promise will never resolve.

上面代码中最后一个有趣的部分是我们不能再进行尝试了—也就是说,我们只是执行了最后的尝试。 在那种情况下,当您尝试调用retry()时,if语句将失败,并且我们将该块reject(err)最后一个我们reject(err) 。 这非常重要,因为如果您不包括此最终错误处理,则外部承诺将永远无法解决。

最后的难题 (The final piece of the puzzle)

Wow, that was a lot to explain and I am sure that was a lot to take in. I want to show you one final snippet though, straight from my codebase, that shows how I leveraged this functionality to deal with tokens expiring during network calls.

哇,这有很多要解释的地方,而且我肯定可以接受。我想向您展示一个最后的片段,直接从我的代码库开始,它显示了我如何利用此功能来处理网络调用期间过期的令牌。

A quick backstory: I am working with the Spotify API, and their auth tokens are only good for 60 minutes. Because of the short lifespan, I found myself having to refresh frequently during development. Realizing that this could be a real problem for users, I thought back to how we had solved this problem during an internship I did.

快速背景知识:我正在使用Spotify API,它们的身份验证令牌仅在60分钟内有效。 由于寿命短,我发现自己在开发过程中必须经常刷新。 意识到这可能对用户来说是一个真正的问题,我回想起我们在实习期间如何解决此问题。

My previous experiences with a similar situation (in Swift on iOS) brought me around to writing this final chunk of code here. I have tested it and it works wonderfully! The user will probably never even know their token expired, which is how it should be, might I add.

我以前在类似情况下的经验(在iOS上的Swift中)使我开始在这里编写最后的代码。 我已经测试过了,效果很好! 用户可能甚至永远不会知道他们的令牌已过期,这应该是我应该添加的。

There is a lot of branching code paths in the above function, and the majority of it is not new. I did my best to explain all the logic in comments. I use this code and have tested it out numerous times and the token refreshes and the response is returned as expected!

上面的函数中有很多分支代码路径,并且大多数不是新的。 我尽力解释了注释中的所有逻辑。 我使用此代码,并对其进行了无数次测试,令牌刷新并按预期返回响应!

Below is a short use case of how I use this function after writing it up.

下面是在编写此函数后如何使用此函数的简短用例。

I really enjoyed writing up this article, and I would love to hear some feedback from anyone who feels up to it. The code above can probably be further optimized in some way, but this is what I am currently using and it works :)

我真的很喜欢写这篇文章,我很乐意听取任何对此有意见的人的反馈。 上面的代码可能可以通过某种方式进一步优化,但这是我当前正在使用的并且可以正常工作:)

I hope this helped at least one person solve a similar problem. If so I would love to hear how you used this!

我希望这可以帮助至少一个人解决类似的问题。 如果是这样,我很想听听您如何使用此功能!

翻译自: https://www.freecodecamp.org/news/creating-a-python-like-decorator-in-javascript-dce3415115c8/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值