promises_在JavaScript中使用Promises时最常见的3个错误

promises

Promises rule JavaScript. Even nowadays, with introduction of async/await, they are still an obligatory knowledge for any JS developer.

承诺规则JavaScript。 即使在当今,随着异步/等待的引入,对于任何JS开发人员来说,它们仍然是必不可少的知识。

But JavaScript differs in how it deals with asynchronicity from other programming languages. Because of that, even developers with a lots of experience can sometimes fall into its traps. I have personally seen great Python or Java programmers making very silly mistakes when coding for Node.js or browsers.

但是JavaScript与其他编程语言的不同之处在于如何处理异步性。 因此,即使具有丰富经验的开发人员有时也会陷入陷阱。 我亲自看到伟大的Python或Java程序员在为Node.js或浏览器编码时犯了非常愚蠢的错误。

Promises in JavaScript have many subtleties which one has to be aware of in order to avoid those mistakes. Some of them will be purely stylistic, but many can introduce actual, difficult to track errors. Because of that, I have decided to compile a short list of the three most common mistakes I have seen developers do, when programming with Promises.

为了避免这些错误,JavaScript中的承诺有许多微妙之处。 其中一些将纯粹是风格上的,但是许多可能引入实际的,难以跟踪的错误。 因此,在使用Promises进行编程时,我决定编写一个清单,列出开发人员遇到的三个最常见的错误。

将所有内容包装在Promise构造函数中 (Wrapping everything in a Promise constructor)

This first mistake is one of the most obvious, and yet I have seen developers do it surprisingly often.

这第一个错误是最明显的错误之一,但是我看到开发人员经常出奇的错误。

When you first learn about Promises, you read about a Promise constructor, which can be used to create new Promises.

当您第一次了解Promise时,您会了解到Promise构造函数,该构造函数可用于创建新的Promises。

Perhaps because people often start learning by wrapping some browser APIs (like setTimeout) in the Promise constructor, it gets ingrained in their minds that the only way to create a Promise is to use the constructor.

也许因为人们通常是通过将一些浏览器API(例如setTimeout )包装在Promise构造函数中来开始学习的,所以在他们心中根深蒂固地创建Promise的唯一方法是使用构造函数。

So as a result they often end up with a code like this:

因此,结果通常是这样的代码:

You can see that in order to do something with the result from somePreviousPromise someone used then, but later decided to wrap it again in a Promise constructor, in order to store that computation in the createdPromise variable, presumably in order to do some more manipulations on that Promise later.

您可以看到,为了对某人then使用的somePreviousPromiseresult进行somePreviousPromise ,后来又决定将其再次包装在Promise构造函数中,以便将该计算存储在createdPromise变量中,大概是为了createdPromise进行更多操作那以后的承诺。

This is of course unnecessary. The whole point of then method is that it itself returns a Promise, that represents executing somePreviousPromise and then executing a callback passed to the then as an argument, after somePreviousPromise gets resolved with a value.

当然这是不必要的。 then方法的全部要点是它本身返回一个Promise,它表示执行somePreviousPromise ,然后在somePreviousPromise得到一个值的解析后,执行作为参数传递给then的回调。

So the previous snippet is roughly equivalent to:

因此,上一片段大致相当于:

Much nicer, isn’t it?

好多了,不是吗?

But why I wrote that it is only roughly equivalent? Where is the difference?

但是,为什么我写它只大致相等呢? 区别在哪里?

It might be hard to spot for the untrained eye, but in fact there is a massive difference in terms of error handling, much more important than the ugly verbosity of the first snippet.

对于未经训练的眼睛来说可能很难发现,但实际上在错误处理方面存在巨大差异,这比第一个摘要的丑陋冗长更为重要。

Let’s say that somePreviousPromise fails for any reason and throws an error. Perhaps that Promise was making a HTTP request underneath and an API responded with a 500 error.

假设somePreviousPromise出于任何原因失败并抛出错误。 可能是Promise在下面发出一个HTTP请求,而API则响应500错误。

It turns out that in the previous snippet, where we wrap a Promise into another Promise, we have no way to catch that error at all. In order to fix that, we would have to introduce following changes:

事实证明,在上一个片段中,我们将一个Promise包装到另一个Promise中,我们根本无法捕获该错误。 为了解决此问题,我们将必须进行以下更改:

We simply added a reject argument to the callback function and then used it by passing it as a second parameter to the then method. It’s very important to remember that then method accepts second, optional parameter for error handling.

我们只是将reject参数添加到回调函数中,然后将其作为第二个参数传递给then方法来使用它。 重要的是要记住, then方法接受第二个可选参数进行错误处理。

Now if somePreviousPromise fails for any reason, reject function will get called and we will be able to handle the error on createdPromise as we would do normally.

现在,如果somePreviousPromise由于任何原因失败,则将调用reject函数,并且我们将能够像createdPromise一样处理createdPromise上的错误。

So does this solve all of the problems? Unfortunately no.

这样可以解决所有问题吗? 抱歉不行。

We handled the errors that can occur in the somePreviousPromise itself, but we still don’t control what happens within the function passed to the then method as a first argument. The code that gets executed in the place where we have put the // do something with the result comment might have some errors. If the code in this place throws any kind of error, it will not be caught by the reject method placed as a second parameter of the then method.

我们处理了somePreviousPromise本身中可能发生的错误,但我们仍然无法控制作为第一个参数传递给then方法的函数中发生的情况。 在我们将// do something with the result注释进行某些// do something with the result的地方执行的代码可能会有一些错误。 如果该位置的代码引发任何类型的错误,则将不会被放置为then方法的第二个参数的reject方法捕获。

That’s because error handling method passed as a second argument to then only reacts to errors that happen earlier in our method chain.

这是因为错误处理方法作为第二个参数传递给它, then仅对方法链中较早发生的错误做出React。

Therefore, the proper (and final) fix will look like this:

因此,正确的(也是最终的)修复将如下所示:

Note that this time we used catch method, which — because it gets called after the first then — will catch any errors that get thrown in the chain above it. So whether the somePreviousPromise or the callback in then will fail — our Promise will handle it as intended in both of those cases.

请注意,这次我们使用catch方法,因为它是第一个方法then被调用的所以它将捕获在其上方的链中抛出的所有错误。 所以,无论是somePreviousPromise或回调then将失败-我们承诺将处理它打算在这两种这些情况。

As you can see, there are many subtleties when wrapping code in Promise constructor. That’s why it’s better to just use then method to create new Promises, as we have shown in a second snippet. Not only it will look nicer, but we will also avoid those corner cases.

如您所见,在Promise构造函数中包装代码时,有很多微妙之处。 这就是为什么最好使用then方法创建新的Promises的原因,如第二段所示。 它不仅看起来会更好,而且我们还将避免那些极端情况。

连续然后vs并行然后 (Consecutive thens vs parallel thens)

Because many programmers have Object Oriented Programming backgrounds, it’s natural for them that a method mutates an object rather than creates a new one.

因为许多程序员都有面向对象的编程背景,所以自然而然,方法会变异一个对象而不是创建一个新对象。

It’s probably why I see people being confused about what exactly happens when you call a then method on a Promise.

这可能就是为什么我看到人们对在Promise上调用then方法时到底发生了什么感到困惑的原因。

Compare those two code snippets:

比较这两个代码段:

Do they do the same thing? It might seem so. After all, both code snippets involve calling then twice on somePromise, right?

他们做同样的事情吗? 似乎是这样。 毕竟,两个代码段都涉及到thensomePromise上调用两次,对吗?

No. It’s a very common misconception. In fact, those two code snippets have a completely different behavior. Not fully understanding what is happening in both of them can lead to tricky mistakes.

不,这是一个非常普遍的误解。 实际上,这两个代码段的行为完全不同。 如果不完全了解两者中正在发生的事情,可能会导致棘手的错误。

As we wrote in a previous section, then method creates a completely new, independent Promise. This means that in the first snippet, second then method is not being called on somePromise, but on a new Promise object, that encapsulates (or represents) waiting for somePromise to get resolved and then calling doFirstThingWithResult right after. And then we add a doSecondThingWithResult callback to this new Promise instance.

正如我们在前面的章节中写道, then方法创建一个全新的,独立的承诺。 这意味着在第一个代码段中,不是在somePromise上调用第二个then方法,而是在一个新的Promise对象上调用该对象,该对象封装(或表示)等待somePromise得到解析,然后立即调用doFirstThingWithResult 。 然后,向此新的Promise实例添加doSecondThingWithResult回调。

In effect, the two callbacks will be executed one after another — we have a guarantee that the second callback will be called only after the first callback finishes execution without any issues. What is more, the first callback will get as an argument a value returned by somePromise, but the second callback will get as an argument whatever is returned from the doFirstThingWithResult function.

实际上,这两个回调将一个接一个地执行-我们保证只有在第一个回调完成执行后,第二个回调才会被调用,而不会出现任何问题。 此外,第一个回调将获得somePromise返回的值作为参数,但是第二个回调将获得doFirstThingWithResult函数返回的值作为参数。

On the other hand, in the second code snipped, we call then method on somePromise twice and basically ignore two new Promises that get returned from that method. Because then was called twice on exactly the same instance of a Promise, we don’t get any guarantees about which callback will get executed first. The order of execution here is undefined.

在另一方面,在第二代码剪断,我们呼吁then在方法somePromise两次,基本上忽视了从那个方法返回两个新的承诺。 因为在完全相同的Promise实例上两次调用了then ,所以我们无法保证首先执行哪个回调。 这里的执行顺序是不确定的。

I sometimes think about it as “parallel” execution, in a sense that the two callbacks should be independent and not rely on any of them being called earlier. But of course in reality JS engines execute only one function at a time — you simply don’t know in which order they will be called.

从某种意义上说,这两个回调应该是独立的,并且不依赖于任何先前调用的回调,我有时将其视为“并行”执行。 但是,当然,实际上,JS引擎一次只执行一个功能-您根本不知道将以什么顺序调用它们。

The second difference is that both doFirstThingWithResult and doSecondThingWithResult in the second snippet will receive the same argument — the value that somePromise gets resolved to. Values returned by both the callbacks are completely ignored in that example.

第二个区别是第二个片段中的doFirstThingWithResultdoSecondThingWithResult都将接收相同的参数— somePromise解析为的值。 在该示例中,两个回调函数返回的值将被完全忽略。

创建后立即执行Promise (Executing a Promise immediately after creation)

This misconception also comes from the fact that most coders are often experienced in Object Oriented Programming.

这种误解还源于这样一个事实,即大多数编码人员通常在面向对象编程中经验丰富。

In that paradigm, it is often considered a good practice to make sure that an object constructor does not perform any actions by itself. For example an object representing a Database should not initiate the connection with the database when its constructor is called with the new keyword.

在这种范例中,通常认为确保对象构造函数自身不执行任何操作是一种好习惯。 例如,代表数据库的对象在使用new关键字调用其构造函数时不应启动与数据库的连接。

Instead, it’s better to provide special method — for example called init — that will explicitly create a connection. This way an object does not perform any unintended actions only because it was initiated. It patiently waits for a programmer to explicitly ask for executing an action.

相反,最好提供一种特殊的方法(例如称为init方法)来显式创建连接。 这样,对象不会仅由于已启动而执行任何意外动作。 它耐心地等待程序员明确要求执行动作。

But that’s not how Promises work.

但这不是Promises的工作方式。

Consider the example:

考虑示例:

You might think that the function making an HTTP request does not get called here, because it is wrapped in a Promise constructor. In fact, many programmers expect that it gets called only after a then method gets executed on a somePromise.

您可能会认为发出HTTP请求的函数未在此处调用,因为它包装在Promise构造函数中。 实际上,许多程序员期望仅在somePromise上执行then方法之后才调用它。

But that’s not true. The callback gets executed immediately when that Promise is created. It means that when you are in the next line after creating somePromis variable, your HTTP request is probably already being executed, or at least scheduled.

但这不是事实。 创建该Promise后,回调将立即执行。 这意味着当您在创建somePromis变量后进入下一行时,您的HTTP请求可能已经在执行或至少已计划。

We say that a Promise is “eager” because it executes an action associated with it as fast as possible. In contrast, many people expect the Promises to be “lazy” — that is to perform an action only when it is absolutely necessary (for example when a then gets called for the first time on a Promise). It’s a misconception. Promises are always eager and never lazy.

我们说Promise是“渴望的”,因为它尽可能快地执行与其关联的动作。 相反,许多人期望的承诺是“懒” -这是只有在绝对必要(当例如要执行的动作then被调用首次在承诺)。 这是一个误解。 承诺永远是渴望的,永远不要偷懒。

But what you should do if you want to execute the Promise later? What if you want hold off with making that HTTP request? Is there some magic mechanism built into the Promises that would allow you to do something like that?

但是,如果以后要执行Promise,应该怎么做? 如果您希望推迟发出该HTTP请求怎么办? Promises中是否内置了某种魔术机制,可让您执行类似的操作?

The answer is more obvious than the developers sometimes would expect. Functions are a lazy mechanism. They are executed only when programmer explicitly calls them with a () bracket syntax. Simply defining a function doesn’t really do anything just yet. So the best way to make a Promise lazy is… to simply wrap it in a function!

答案比开发人员有时期望的更为明显。 函数是一种惰性机制。 仅当程序员使用()括号语法显式调用它们时,才执行它们。 仅仅定义一个函数实际上还没有做任何事情。 因此,使Promise成为懒惰的最佳方法是……将其简单地包装在函数中!

Take a look:

看一看:

Now we wrapped the same Promise constructor call in a function. Because of that nothing really gets called yet. We also changed a variable name from somePromise to createSomePromise, because it is not really a Promise anymore — it is a function creating and returning a Promise.

现在,我们将相同的Promise构造函数调用包装在一个函数中。 因此,还没有真正得到调用。 我们还将变量名从somePromisecreateSomePromise ,因为它不再是Promise了-它是创建并返回Promise的函数。

The Promise constructor — and hence the callback function with a HTTP request — will only be called when we execute that function. So now we have a lazy Promise, that gets executed only when we really want it.

Promise构造函数(以及带有HTTP请求的回调函数)仅在执行该函数时被调用。 因此,现在我们有了一个懒惰的Promise,只有在我们真正想要它时才执行它。

What is more, note that for free we got another capability. We can easily create another Promise, that performs the same action.

此外,请注意,免费提供了另一种功能。 我们可以轻松创建另一个执行相同动作的Promise。

If for some weird reason we would like to make the same HTTP call twice and execute those calls concurrently, we can just call the createSomePromise function twice, one immediately after another. Or if a request fails for any reason, we can retry it, using the very same function.

如果出于某种奇怪的原因,我们希望进行两次相同的HTTP调用并同时执行这些调用,则可以仅两次调用一次createSomePromise函数,一次又一次。 或者,如果请求由于任何原因失败,我们可以使用相同的功能重试。

This shows that it’s extremely handy to wrap Promises in functions (or methods) and hence it is a pattern that should become natural for a JavaScript developer.

这表明将Promises包装在函数(或方法)中非常方便,因此对于JavaScript开发人员来说,这是一种自然的模式。

Ironically, if you have read my article on Promises vs Observables, you know that programmers being introduced to Rx.js often make an opposite mistake. They code Observables as if they are eager (like Promises), while in fact they are lazy. So, for example, wrapping Observables in a function or a method often does not make any sense and in fact can even be harmful.

具有讽刺意味的是,如果您阅读了有关Promises vs Observables的文章,那么您就会知道,被介绍给Rx.js的程序员经常会犯一个相反的错误。 他们对Observable进行编码,就好像它们渴望一样(例如Promises),而实际上它们是懒惰的。 因此,例如,将Observables封装在函数或方法中通常没有任何意义,实际上甚至是有害的。

结论 (Conclusion)

I have shown you three types of mistakes that I have often seen being made by developers who knew Promises in JavaScript only superficially.

我向您展示了三种类型的错误,这些错误是我经常看到的,他们仅仅从表面上了解JavaScript中的Promises。

Are there any interesting types of mistakes that you have encountered either in your code or in the code of others? If so, share them in the comment.

您的代码或其他代码中是否遇到过任何有趣的错误类型? 如果是这样,请在评论中分享。

If you enjoyed this article, considered following me on Twitter, where I will be posting more articles on JavaScript programming.

如果您喜欢这篇文章,请考虑在Twitter上关注我,我将在其中发布更多有关JavaScript编程的文章。

Thanks for reading!

谢谢阅读!

翻译自: https://medium.com/@mpodlasin/3-most-common-mistakes-in-using-promises-in-javascript-575fc31939b6

promises

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值