博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
异步JavaScript的演变:从回调到承诺,再到异步/等待
阅读量:2536 次
发布时间:2019-05-11

本文共 37637 字,大约阅读时间需要 125 分钟。

One of my favorite sites is - it's simple, effective, and has been doing its job well since it launched in 1997. Even more remarkable, over the last 20 years, there's a good chance this site has never had a bug. Why? Because it's all static.

我最喜欢的网站之一是 ,它简单,有效,自1997年成立以来一直做得很好。更值得注意的是,在过去的20年中,该网站很可能从未出现过错误。 为什么? 因为这都是静态的。

It's been pretty much the same since it launched over 20 years ago. Turns out sites are pretty simple to build if you have all of your data up front. Unfortunately, most sites now days don't. To compensate for this, we've invented "patterns" for handling fetching external data for our apps. Like most things, these patterns each have tradeoffs that have changed over time. In this post we'll break down the pros and cons of three of the most common patterns, Callbacks, Promises, and Async/Await and talk about their significance and progression from a historical context.

自从20多年前发布以来,它几乎是相同的。 事实证明,如果您预先拥有所有数据,则站点的构建非常简单。 不幸的是,现在大多数站点都没有。 为了弥补这一点,我们发明了“模式”来处理获取应用程序的外部数据。 像大多数事物一样,这些模式的权衡也随时间而改变。 在这篇文章中,我们将分解三种最常见的模式,即CallbacksPromisesAsync/Await优缺点,并从历史的角度讨论它们的重要性和发展。

Let's start with the OG of these data fetching patterns, Callbacks.

让我们从这些数据获取模式的OG(回调)开始。

回呼 (Callbacks)

I'm going to assume you know exactly 0 about callbacks. If I'm assuming wrong, just scroll down a bit.

我假设您对回调完全了解0。 如果我假设做错了,请向下滚动一点。

When I was first learning to program, it helped me to think about functions as machines. These machines can do anything you want them to. They can even accept input and return a value. Each machine has a button on it that you can press when you want the machine to run, ().

当我第一次学习编程时,它帮助我考虑了机器的功能。 这些机器可以执行您希望它们执行的任何操作。 他们甚至可以接受输入并返回值。 每台计算机上都有一个按钮,您可以在要运行计算机时按一下()。

function add (x, y) {  return x + y}add(2,3) // 5 - Press the button, run the machine.

Whether I press the button, you press the button, or someone else presses the button doesn't matter. Whenever the button is pressed, like it or not, the machine is going to run.

无论按下按钮, 按下该按钮,或其他人按下按钮无所谓。 每当按下按钮时,无论是否按下按钮,机器都将运行。

function add (x, y) {  return x + y}const me = addconst you = addconst someoneElse = addme(2,3) // 5 - Press the button, run the machine.you(2,3) // 5 - Press the button, run the machine.someoneElse(2,3) // 5 - Press the button, run the machine.

In the code above we assign the add function to three different variables, me, you, and someoneElse. It's important to note that the original add and each of the variables we created are pointing to the same spot in memory. They're literally the exact same thing under different names. So when we invoke me, you, or someoneElse,  it's as if we're invoking add.

在上面的代码中,我们将add函数分配给三个不同的变量meyousomeoneElse 。 重要的是要注意,原始的add和我们创建的每个变量都指向内存中的同一位置。 从字面上看,它们是完全一样的东西,只是名称不同。 因此,当我们调用meyousomeoneElse ,就好像我们在调用add

Now what if we take our add machine and pass it to another machine? Remember, it doesn't matter who presses the () button, if it's pressed, it's going to run.

现在,如果我们将add计算机传递给另一台计算机该怎么办? 请记住,谁按下()按钮并不重要,如果按下,它将开始运行。

function add (x, y) {  return x + y}function addFive (x, addReference) {  return addReference(x, 5) // 15 - Press the button, run the machine.}addFive(10, add) // 15

Your brain might have got a little weird on this one, nothing new is going on here though. Instead of "pressing the button" on add, we pass add as an argument to addFive, rename it addReference, and then we "press the button" or invoke it.

您的大脑可能对此感到有些奇怪,但是这里没有发生任何新变化。 而不是在add上“按下按钮”,我们将add作为参数传递给addFive ,将其重命名为addReference ,然后我们“按下按钮”或调用它。

This highlights some important concepts of the JavaScript language. First, just as you can pass a string or a number as an argument to a function, so too can you pass a reference to a function as an argument. When you do this the function you're passing as an argument is called a callback function and the function you're passing the callback function to is called a higher order function.

这突出了JavaScript语言的一些重要概念。 首先,就像可以将字符串或数字作为参数传递给函数一样,也可以将对函数的引用传递作为参数。 当您执行此操作时,作为参数传递的函数称为回调函数,而将回调函数传递给的函数称为高阶函数

Because vocabulary is important, here's the same code with the variables re-named to match the concepts they're demonstrating.

因为词汇很重要,所以下面是相同的代码,其中的变量已重命名以匹配它们所演示的概念。

function add (x,y) {  return x + y}function higherOrderFunction (x, callback) {  return callback(x, 5)}higherOrderFunction(10, add)

This pattern should look familiar, it's everywhere. If you've ever used any of the JavaScript Array methods, you've used a callback. If you've ever used lodash, you've used a callback. If you've ever used jQuery, you've used a callback.

这种模式应该看起来很熟悉,无处不在。 如果您曾经使用过任何JavaScript Array方法,则可以使用回调。 如果您曾经使用lodash,则使用过回调。 如果您曾经使用过jQuery,则可以使用回调。

[1,2,3].map((i) => i + 5)_.filter([1,2,3,4], (n) => n % 2 === 0 );$('#btn').on('click', () =>  console.log('Callbacks are everywhere'))

In general, there are two popular use cases for callbacks. The first, and what we see in the .map and _.filter examples, is a nice abstraction over turning one value into another. We say "Hey, here's an array and a function. Go ahead and get me a new value based on the function I gave you". The second, and what we see in the jQuery example, is delaying execution of a function until a particular time. "Hey, here's this function. Go ahead and invoke it whenever the element with an id of btn is clicked." It's this second use case that we're going to focus on, "delaying execution of a function until a particular time".

通常,回调有两种流行的用例。 第一个,以及我们在.map_.filter示例中看到的,是将一个值转换为另一个值的一种很好的抽象。 我们说:“嘿,这是一个数组和一个函数。根据我给你的函数,继续给我一个新值”。 第二个,以及我们在jQuery示例中看到的,是将函数的执行延迟到特定时间。 “嘿,这是这个函数。只要单击ID为btn的元素,就继续调用它。” 这是我们要关注的第二个用例,“将函数的执行延迟到特定的时间”。

Right now we've only looked at examples that are synchronous. As we talked about at the beginning of this post, most of the apps we build don't have all the data they need up front. Instead, they need to fetch external data as the user interacts with the app. We've just seen how callbacks can be a great use case for this because, again, they allow you to "delay execution of a function until a particular time". It doesn't take much imagination to see how we can adapt that sentence to work with data fetching. Instead of delaying execution of a function until a particular time, we can delay execution of a function until we have the data we need. Here's probably the most popular example of this, jQuery's getJSON method.

现在,我们只看了同步的示例。 正如我们在本文开头所讨论的那样,我们构建的大多数应用程序都没有预先获取所需的所有数据。 相反,他们需要在用户与应用程序交互时获取外部数据。 我们刚刚看到了回调如何成为一个很好的用例,因为同样,回调允许您“将函数的执行延迟到特定的时间”。 不需要太多的想象力就可以知道我们如何使该句子适合于数据提取。 可以将函数的执行延迟到拥有所需的数据之前 ,而不是将函数的执行延迟到特定的时间 。 这可能是最流行的示例,即jQuery的getJSON方法。

// updateUI and showError are irrelevant.// Pretend they do what they sound like.const id = 'tylermcginnis'$.getJSON({  url: `https://api.github.com/users/${id}`,  success: updateUI,  error: showError,})

We can't update the UI of our app until we have the user's data. So what do we do? We say, "Hey, here's an object. If the request succeeds, go ahead and call success passing it the user's data. If it doesn't, go ahead and call error passing it the error object. You don't need to worry about what each method does, just be sure to call them when you're supposed to". This is a perfect demonstration of using a callback for async requests.

在拥有用户数据之前,我们无法更新应用程序的用户界面。 那么我们该怎么办? 我们说:“嘿,这是一个对象。如果请求成功,则继续并调用success ,将用户数据传递给它。如果未成功,则继续并调用errorerror传递给错误对象。您不必担心关于每种方法的作用,只需确保在需要时调用它们即可。” 这是对异步请求使用回调的完美演示。



At this point we've learned about what callbacks are and how they can be beneficial both in synchronous and asynchronous code. What we haven't talked yet is the dark side of callbacks. Take a look at this code below. Can you tell what's happening?

至此,我们已经了解了什么是回调以及它们如何在同步和异步代码中受益。 我们还没有谈论的是回调的阴暗面。 看下面的这段代码。 你能告诉发生什么事吗?

// updateUI, showError, and getLocationURL are irrelevant.// Pretend they do what they sound like.const id = 'tylermcginnis'$("#btn").on("click", () => {  $.getJSON({    url: `https://api.github.com/users/${id}`,    success: (user) => {      $.getJSON({        url: getLocationURL(user.location.split(',')),        success (weather) {          updateUI({            user,            weather: weather.query.results          })        },        error: showError,      })    },    error: showError  })})

If it helps, you can play around with the .

如果有帮助,您可以在 。

Notice we've added a few more layers of callbacks. First we're saying don't run the initial AJAX request until the element with an id of btn is clicked. Once the button is clicked, we make the first request. If that request succeeds, we make a second request. If that request succeeds, we invoke the updateUI method passing it the data we got from both requests. Regardless of if you understood the code at first glance or not, objectively it's much harder to read than the code before. This brings us to the topic of "Callback Hell".

注意,我们添加了更多的回调层。 首先,我们要说的是在单击ID为btn的元素之前不要运行初始AJAX请求。 单击该按钮后,我们发出第一个请求。 如果该请求成功,我们将发出第二个请求。 如果该请求成功,我们将调用updateUI方法,将从两个请求中获取的数据传递给该方法。 不管您乍看之下是否了解代码,客观上来说,它都比以前的代码难读。 这将我们带入“回调地狱”主题。

As humans, we naturally think sequentially. When you have nested callbacks inside of nested callbacks, it forces you out of your natural way of thinking. Bugs happen when there's a disconnect between how your software is read and how you naturally think.

作为人类,我们自然会顺序思考。 当您在嵌套回调中嵌套了回调时,这会迫使您脱离自然思维方式。 当您的软件阅读方式与您的自然思维方式脱节时,就会发生错误。

Like most solutions to software problems, a commonly prescribed approach for making "Callback Hell" easier to consume is to modularize your code.

像大多数软件问题解决方案一样,使“回调地狱”更易于使用的通常规定的方法是模块化代码。

function getUser(id, onSuccess, onFailure) {  $.getJSON({    url: `https://api.github.com/users/${id}`,    success: onSuccess,    error: onFailure  })}function getWeather(user, onSuccess, onFailure) {  $.getJSON({    url: getLocationURL(user.location.split(',')),    success: onSuccess,    error: onFailure,  })}$("#btn").on("click", () => {  getUser("tylermcginnis", (user) => {    getWeather(user, (weather) => {      updateUI({        user,        weather: weather.query.results      })    }, showError)  }, showError)})

If it helps, you can play around with the .

如果有帮助,您可以在 。

OK, the function names help us understand what's going on, but is it objectively "better"? Not by much. We've put a band-aid over the readability issue of Callback Hell. The problem still exists that we naturally think sequentially and, even with the extra functions, nested callbacks break us out of that sequential way of thinking.

好的,函数名称可以帮助我们了解发生了什么,但是客观上它是“更好”的吗? 不多。 我们对Callback Hell的可读性问题进行了创可贴。 问题仍然存在,我们自然会按顺序思考,即使有了额外的功能,嵌套回调也会使我们脱离按顺序思考的方式。



The next issue of callbacks has to do with . When you write a callback, you're assuming that the program you're giving the callback to is responsible and will call it when (and only when) it's supposed to. You're essentially inverting the control of your program over to another program. When you're dealing with libraries like jQuery, lodash, or even vanilla JavaScript, it's safe to assume that the callback function will be invoked at the correct time with the correct arguments. However, for many third party libraries, callback functions are the interface for how you interact with them. It's entirely plausible that a third party library could, whether on purpose or accidentally, break how they interact with your callback.

下一个回调问题与 。 编写回调时,您假定要提供该回调的程序负责,并且将在应有的时候(且仅在应有的时候)调用它。 实质上,您是将程序的控制权移交给另一个程序。 当您处理jQuery,lodash甚至是香草JavaScript之类的库时,可以安全地假设将在正确的时间使用正确的参数调用回调函数。 但是,对于许多第三方库,回调函数是您与它们进行交互的接口。 第三方库有可能(无论是有意还是无意)破坏了它们与回调的交互方式,这完全是合理的。

function criticalFunction () {  // It's critical that this function  // gets called and with the correct  // arguments.}thirdPartyLib(criticalFunction)

Since you're not the one calling criticalFunction, you have 0 control over when and with what argument it's invoked. Most of the time this isn't an issue, but when it is, it's a big one.

由于您不是一个调用criticalFunction ,因此可以在何时以及使用什么参数进行调用时控制0。 在大多数情况下,这不是问题,但当它成为一个大问题时。



承诺 (Promises)

Have you ever been to a busy restaurant without a reservation? When this happens, the restaurant needs a way to get back in contact with you when a table opens up. Historically, they'd just take your name and yell it when your table was ready. Then, as naturally occurs, they decided to start getting fancy. One solution was, instead of taking your name, they'd take your number and text you once a table opened up. This allowed you to be out of yelling range but more importantly, it allowed them to target your phone with ads whenever they wanted. Sound familiar? It should! OK, maybe it shouldn't. It's a metaphor for callbacks! Giving your number to a restaurant is just like giving a callback function to a third party service. You expect the restaurant to text you when a table opens up, just like you expect the third party service to invoke your function when and how they said they would. Once your number or callback function is in their hands though, you've lost all control.

您是否曾经毫无保留地去过繁忙的餐厅? 发生这种情况时,餐厅需要一种方法在餐桌打开时重新与您联系。 从历史上看,他们只是取您的名字,并在您准备好餐桌时大喊大叫。 然后,自然而然地,他们决定开始幻想。 一种解决方法是,一旦打开桌子,他们就会使用您的电话号码和文字来代替您的名字。 这样一来,您就可以大喊大叫,但更重要的是,他们可以根据需要在广告中定位您的手机。 听起来有点熟? 这应该! 好吧,也许不应该。 这是回调的隐喻! 将您的号码提供给餐厅就像给第三方服务提供回调功能。 希望餐厅在打开表格时向您发送短信,就像您希望第三方服务在何时,如何以他们的方式调用您的功能时一样。 但是,一旦您掌握了数字或回调函数,您将失去所有控制权。

Thankfully, there is another solution that exists. One that, by design, allows you to keep all the control. You've probably even experienced it before - it's that little buzzer thing they give you. You know, this one.

值得庆幸的是,还有另一种解决方案。 一种设计使您可以保留所有控件。 您甚至可能以前就已经经历过-这就是他们给您带来的嗡嗡声。 你知道,这个。

If you've never used one before, the idea is simple. Instead of taking your name or number, they give you this device. When the device starts buzzing and glowing, your table is ready. You can still do whatever you'd like as you're waiting for your table to open up, but now you don't have to give up anything. In fact, it's the exact opposite. They have to give you something. There is no inversion of control.

如果您以前从未使用过,那么这个想法很简单。 他们会使用您的设备代替您的姓名或电话号码。 设备开始嗡嗡作响并发光时,您的桌子已经准备就绪。 在等待打开桌子时,您仍然可以做任何您想做的事,但是现在您不必放弃任何事情。 实际上,情况恰恰相反。 他们必须给一些东西。 没有控制权的倒置。

The buzzer will always be in one of three different states - pending, fulfilled, or rejected.

蜂鸣器将始终处于三种不同状态之一- pending ,已fulfilled或已rejected

pending is the default, initial state. When they give you the buzzer, it's in this state.

pending是默认的初始状态。 当他们给您蜂鸣器时,它就处于这种状态。

fulfilled is the state the buzzer is in when it's flashing and your table is ready.

蜂鸣器闪烁且您的桌子准备就绪时,蜂鸣器处于已fulfilled状态。

rejected is the state the buzzer is in when something goes wrong. Maybe the restaurant is about to close or they forgot someone rented out the restaurant for the night.

出现故障时,蜂鸣器将处于rejected状态。 也许餐厅快要关门了,或者他们忘了有人把餐厅租了一晚。

Again, the important thing to remember is that you, the receiver of the buzzer, have all the control. If the buzzer gets put into fulfilled, you can go to your table. If it gets put into fulfilled and you want to ignore it, cool, you can do that too. If it gets put into rejected, that sucks but you can go somewhere else to eat. If nothing ever happens and it stays in pending, you never get to eat but you're not actually out anything.

同样,要记住的重要一点是,蜂鸣器的接收者拥有所有控制权。 如果蜂鸣器变得fulfilled ,您可以上桌。 如果它fulfilled并且您想忽略它,那么酷,您也可以这样做。 如果它被rejected ,那很糟,但是您可以去其他地方吃饭。 如果什么都没有发生,并且它处于pending ,则您永远都不会吃饭,但实际上您什么都没吃。

Now that you're a master of the restaurant buzzer thingy, let's apply that knowledge to something that matters.

既然您是餐厅蜂鸣器的专家,那么让我们将该知识应用于重要的事情。

If giving the restaurant your number is like giving them a callback function, receiving the little buzzy thing is like receiving what's called a "Promise".

如果给餐厅提供您的电话号码就像给他们提供了回调函数,那么接收嗡嗡声就好像收到所谓的“承诺”一样。

As always, let's start with why. Why do Promises exist? They exist to make the complexity of making asynchronous requests more manageable. Exactly like the buzzer, a Promise can be in one of three states, pending, fulfilled or rejected. Unlike the buzzer, instead of these states representing the status of a table at a restaurant, they represent the status of an asynchronous request.

与往常一样,让我们​​从为什么开始。 为什么存在承诺? 它们的存在使异步请求的管理变得更加复杂。 就像蜂鸣器一样, Promise可以处于三种状态之一,即pending ,已fulfilled或已rejected 。 与蜂鸣器不同,这些状态不是代表餐厅中餐桌的状态,而是代表异步请求的状态。

If the async request is still ongoing, the Promise will have a status of pending. If the async request was successfully completed, the Promise will change to a status of fulfilled. If the async request failed, the Promise will change to a status of rejected. The buzzer metaphor is pretty spot on, right?

如果异步请求仍在进行中,则Promise的状态将为pending 。 如果异步请求已成功完成,则Promise将会更改为已fulfilled状态。 如果异步请求失败,则Promise会更改为rejected状态。 蜂鸣器的隐喻很漂亮,对吧?

Now that you understand why Promises exist and the different states they can be in, there are three more questions we need to answer.

既然您了解了承诺存在的原因以及它们可能处于的不同状态,那么我们还需要回答三个问题。

  1. How do you create a Promise?

    您如何创建一个承诺?
  2. How do you change the status of a promise?

    您如何更改承诺的状态?
  3. How do you listen for when the status of a promise changes?

    当承诺的状态发生变化时,您该如何听?
1)如何创建一个承诺? (1) How do you create a Promise?)

This one is pretty straight forward. You create a new instance of Promise.

这很简单。 您创建一个Promisenew实例。

const promise = new Promise()
2)您如何更改承诺的状态? (2) How do you change the status of a promise?)

The Promise constructor function takes in a single argument, a (callback) function. This function is going to be passed two arguments, resolve and reject.

Promise构造函数接受一个参数,即(回调)函数。 该函数将传递两个参数resolvereject

resolve - a function that allows you to change the status of the promise to fulfilled

resolve -一种功能,可让您更改承诺fulfilled的状态

reject - a function that allows you to change the status of the promise to rejected.

reject -一种功能,使您可以将承诺的状态更改为已rejected

In the code below, we use setTimeout to wait 2 seconds and then invoke resolve. This will change the status of the promise to fulfilled.

在下面的代码中,我们使用setTimeout等待2秒,然后调用resolve 。 这将改变承诺fulfilled的状态。

const promise = new Promise((resolve, reject) => {  setTimeout(() => {    resolve() // Change status to 'fulfilled'  }, 2000)})

We can see this change in action by logging the promise right after we create it and then again roughly 2 seconds later after resolve has been called.

我们可以通过在创建承诺后立即记录承诺,然后在调用resolve大约2秒钟后再次记录承诺,来观察到这种变化。

Notice the promise goes from <pending> to <resolved>.

请注意,promise从<pending>变为<resolved>

3)当承诺状态发生变化时,您如何聆听? (3) How do you listen for when the status of a promise changes?)

In my opinion this is the most important question. It's cool we know how to create a promise and change its status, but that's worthless if we don't know how to do anything after the status changes.

我认为这是最重要的问题。 我们知道如何创建承诺并更改其状态,这很酷,但是如果状态变化后我们不知道如何做,那将毫无价值。

One thing we haven't talked about yet is what a promise actually is. When you create a new Promise, you're really just creating a plain old JavaScript object. This object can invoke two methods, then, and catch. Here's the key. When the status of the promise changes to fulfilled, the function that was passed to .then will get invoked. When the status of a promise changes to rejected, the function that was passed to .catch will be invoked. What this means is that once you create a promise, you'll pass the function you want to run if the async request is successful to .then. You'll pass the function you want to run if the async request fails to .catch.

我们尚未谈论的一件事实际上是诺言。 创建new Promise ,实际上只是在创建一个普通的旧JavaScript对象。 该对象可以调用两个方法thencatch 。 这是关键。 当promise的状态更改为fulfilled ,传递给.then的函数将被调用。 当promise的状态更改为rejected ,将调用传递给.catch的函数。 这意味着一旦创建了一个Promise,如果异步请求成功,您将把想要运行的函数传递给.then 。 如果异步请求未能.catch您将传递要运行的函数。

Let's take a look at an example. We'll use setTimeout again to change the status of the promise to fulfilled after two seconds (2000 milliseconds).

让我们看一个例子。 我们将再次使用setTimeout更改两秒钟(2000毫秒)后要fulfilled的承诺状态。

function onSuccess () {  console.log('Success!')}function onError () {  console.log('?')}const promise = new Promise((resolve, reject) => {  setTimeout(() => {    resolve()  }, 2000)})promise.then(onSuccess)promise.catch(onError)

If you run the code above you'll notice that roughly 2 seconds later, you'll see "Success!" in the console. Again the reason this happens is because of two things. First, when we created the promise, we invoked resolve after ~2000 milliseconds - this changed the status of the promise to fulfilled. Second, we passed the onSuccess function to the promises' .then method. By doing that we told the promise to invoke onSuccess when the status of the promise changed to fulfilled which it did after ~2000 milliseconds.

如果您运行上面的代码,大约2秒钟后,您会看到“成功!”。 在控制台中。 同样,发生这种情况的原因是由于两件事。 首先,当我们创建了承诺,我们调用的resolve 〜2000年以后毫秒-这改变了承诺的状态fulfilled 。 其次,我们将onSuccess函数传递给promises的.then方法。 通过这样做,我们告诉了onSuccessonSuccess的状态更改为fulfilled时调用onSuccess ,它在大约2000毫秒后执行。

Now let's pretend something bad happened and we wanted to change the status of the promise to rejected. Instead of calling resolve, we would call reject.

现在,让我们假装发生了不好的事情,我们希望将诺言的状态更改为rejected 。 与其说是resolvereject

function onSuccess () {  console.log('Success!')}function onError () {  console.log('?')}const promise = new Promise((resolve, reject) => {  setTimeout(() => {    reject()  }, 2000)})promise.then(onSuccess)promise.catch(onError)

Now this time instead of the onSuccess function being invoked, the onError function will be invoked since we called reject.

现在这一次不是调用onSuccess函数,而是调用onError函数,因为我们调用了reject



Now that you know your way around the Promise API, let's start looking at some real code.

现在您已经了解了Promise API的使用方式,让我们开始看一些真实的代码。

Remember the last async callback example we saw earlier?

还记得我们之前看到的最后一个异步回调示例吗?

function getUser(id, onSuccess, onFailure) {  $.getJSON({    url: `https://api.github.com/users/${id}`,    success: onSuccess,    error: onFailure  })}function getWeather(user, onSuccess, onFailure) {  $.getJSON({    url: getLocationURL(user.location.split(',')),    success: onSuccess,    error: onFailure,  })}$("#btn").on("click", () => {  getUser("tylermcginnis", (user) => {    getWeather(user, (weather) => {      updateUI({        user,        weather: weather.query.results      })    }, showError)  }, showError)})

Is there anyway we could use the Promise API here instead of using callbacks? What if we wrap our AJAX requests inside of a promise? Then we can simply resolve or reject depending on how the request goes. Let's start with getUser.

无论如何,我们可以在这里使用Promise API而不是使用回调吗? 如果我们将AJAX请求包装在promise中怎么办? 然后,我们可以根据请求的进行方式简单地resolvereject 。 让我们从getUser开始。

function getUser(id) {  return new Promise((resolve, reject) => {    $.getJSON({      url: `https://api.github.com/users/${id}`,      success: resolve,      error: reject    })  })}

Nice. Notice that the parameters of getUser have changed. Instead of receiving id, onSuccess, and onFailure, it just receives id. There's no more need for those other two callback functions because we're no longer inverting control. Instead, we use the Promise's resolve and reject functions. resolve will be invoked if the request was successful, reject will be invoked if there was an error.

真好 请注意, getUser的参数已更改。 它只接收id而不是接收idonSuccessonFailure 。 不再需要其他两个回调函数,因为我们不再需要反转控制了。 相反,我们使用Promise的resolvereject函数。 如果请求成功,则将调用resolve如果发生错误,则将调用reject

Next let's refactor getWeather. We'll follow the same strategy here. Instead of taking in onSuccess and onFailure callback functions, we'll use resolve and reject.

接下来,让我们重构getWeather 。 在这里,我们将遵循相同的策略。 代替使用onSuccessonFailure回调函数,我们将使用resolvereject

function getWeather(user) {  return new Promise((resolve, reject) => {    $.getJSON({      url: getLocationURL(user.location.split(',')),      success: resolve,      error: reject,    })  })}

Looking good. Now the last thing we need to update is our click handler. Remember, here's the flow we want to take.

看起来不错。 现在,我们需要更新的最后一件事是单击处理程序。 请记住,这是我们要采取的流程。

  1. Get the user's information from the Github API.

    从Github API获取用户信息。
  2. Use the user's location to get their weather from the Yahoo Weather API.

    使用用户的位置从Yahoo Weather API获取其天气。
  3. Update the UI with the users info and their weather.

    使用用户信息及其天气更新UI。

Let's start with #1 - getting the user's information from the Github API.

让我们从#1开始-从Github API获取用户信息。

$("#btn").on("click", () => {  const userPromise = getUser('tylermcginnis')  userPromise.then((user) => {  })  userPromise.catch(showError)})

Notice that now instead of getUser taking in two callback functions, it returns us a promise that we can call .then and .catch on. If .then is called, it'll be called with the user's information. If .catch is called, it'll be called with the error.

注意,现在代替getUser分两次服用回调函数,它返回我们的承诺,我们可以调用.then.catch上。 如果.then被调用,它将与用户的信息一起被调用。 如果.catch被调用,它将被错误调用。

Next let's do #2 - Use the user's location to get their weather.

接下来,让我们做#2-使用用户的位置获取他们的天气。

$("#btn").on("click", () => {  const userPromise = getUser('tylermcginnis')  userPromise.then((user) => {    const weatherPromise = getWeather(user)    weatherPromise.then((weather) => {    })    weatherPromise.catch(showError)  })  userPromise.catch(showError)})

Notice we follow the exact same pattern we did in #1 but now we invoke getWeather passing it the user object we got from userPromise.

注意,我们遵循与#1完全相同的模式,但是现在我们调用getWeather将它传递给从userPromise获得的user对象。

Finally, #3 - Update the UI with the users info and their weather.

最后,#3-使用用户信息和他们的天气更新UI。

$("#btn").on("click", () => {  const userPromise = getUser('tylermcginnis')  userPromise.then((user) => {    const weatherPromise = getWeather(user)    weatherPromise.then((weather) => {      updateUI({        user,        weather: weather.query.results      })    })    weatherPromise.catch(showError)  })  userPromise.catch(showError)})

you can play around with.

您可以使用 。

Our new code is better, but there are still some improvement we can make. Before we can make those improvements though, there are two more features of promises you need to be aware of, chaining and passing arguments from resolve to then.

我们的新代码更好 ,但是仍然可以做一些改进。 但是,在我们进行这些改进之前,您需要了解Promise的另外两个功能,即将参数从resolve链接到then

链式 (Chaining)

Both .then and .catch will return a new promise. That seems like a small detail but it's important because it means that promises can be chained.

这两种.then.catch将返回一个新的承诺。 这似乎很小,但很重要,因为这意味着可以将诺言链接起来。

In the example below, we call getPromise which returns us a promise that will resolve in at least 2000 milliseconds. From there, because .then will return a promise, we can continue to chain our .thens together until we throw a new Error which is caught by the .catch method.

在下面的示例中,我们调用getPromise ,它向我们返回一个承诺,该承诺将至少在2000毫秒内解决。 从那里,因为.then会返回一个承诺,我们可以继续我们的链条.then小号在一起,直到我们抛出一个new Error这是由抓.catch方法。

function getPromise () {  return new Promise((resolve) => {    setTimeout(resolve, 2000)  })}function logA () {  console.log('A')}function logB () {  console.log('B')}function logCAndThrow () {  console.log('C')  throw new Error()}function catchError () {  console.log('Error!')}getPromise()  .then(logA) // A  .then(logB) // B  .then(logCAndThrow) // C  .catch(catchError) // Error!

Cool, but why is this so important? Remember back in the callback section we talked about one of the downfalls of callbacks being that they force you out of your natural, sequential way of thinking. When you chain promises together, it doesn't force you out of that natural way of thinking because chained promises are sequential. getPromise runs then logA runs then logB runs then....

太酷了,但这为什么如此重要? 记得在回调部分中,我们谈到了回调的缺点之一是它们迫使您脱离自然的,顺序的思维方式。 当您将承诺链接在一起时,它不会迫使您脱离自然的思维方式,因为链接的承诺是顺序的。 getPromise runs then logA runs then logB runs then...

Just so you can see one more example, here's a common use case when you use the fetch API. fetch will return you a promise that will resolve with the HTTP response. To get the actual JSON, you'll need to call .json. Because of chaining, we can think about this in a sequential manner.

就像这样,您可以看到另一个示例,这是使用fetch API时的一个常见用例。 fetch将返回一个承诺,该承诺将通过HTTP响应来解决。 要获取实际的JSON,您需要调用.json 。 由于链接,我们可以按顺序考虑这一点。

fetch('/api/user.json')  .then((response) => response.json())  .then((user) => {    // user is now ready to go.  })

Now that we know about chaining, let's refactor our getUser/getWeather code from earlier to use it.

既然我们知道了链接,那么让我们从较早的地方重构我们的getUser / getWeather代码以使用它。

function getUser(id) {  return new Promise((resolve, reject) => {    $.getJSON({      url: `https://api.github.com/users/${id}`,      success: resolve,      error: reject    })  })}function getWeather(user) {  return new Promise((resolve, reject) => {    $.getJSON({      url: getLocationURL(user.location.split(',')),      success: resolve,      error: reject,    })  })}$("#btn").on("click", () => {  getUser("tylermcginnis")    .then(getWeather)    .then((weather) => {      // We need both the user and the weather here.      // Right now we just have the weather      updateUI() // ????    })    .catch(showError)})

It looks much better, but now we're running into an issue. Can you spot it? In the second .then we want to to call updateUI. The problem is we need to pass updateUI both the user and the weather. Currently how we have it set up, we're only receiving the weather, not the user. Somehow we need to figure out a way to make it so the promise that getWeather returns is resolved with both the user and the weather.

看起来好多了,但是现在我们遇到了一个问题。 你能发现吗? 在第二个.then我们要调用updateUI 。 问题是我们需要同时向userweather传递updateUI 。 目前,我们是如何进行设置的,我们只接收weather ,而不是user 。 我们需要以某种方式找到一种实现方法,以便使getWeather返回的承诺由userweather

Here's the key. resolve is just a function. Any arguments you pass to it will be passed along to the function given to .then. What that means is that inside of getWeather, if we invoke resolve ourself, we can pass to it weather and user. Then, the second .then method in our chain will receive both user and weather as an argument.

这是关键。 resolve只是一个功能。 您传递给它的任何参数都将传递给.then给出的函数。 这意味着在getWeather内部,如果我们调用自己的resolve ,则可以将weatheruser传递给它。 然后,链中的第二个.then方法将同时接收userweather作为参数。

function getWeather(user) {  return new Promise((resolve, reject) => {    $.getJSON({      url: getLocationURL(user.location.split(',')),      success(weather) {        resolve({ user, weather: weather.query.results })      },      error: reject,    })  })}$("#btn").on("click", () => {  getUser("tylermcginnis")    .then(getWeather)    .then((data) => {      // Now, data is an object with a      // "weather" property and a "user" property.      updateUI(data)    })    .catch(showError)})

You can play around with the

您可以在

It's in our click handler where you really see the power of promises shine compared to callbacks.

与回调相比,在点击处理程序中,您真正看到的是Promise的力量。

// Callbacks ?getUser("tylermcginnis", (user) => {  getWeather(user, (weather) => {    updateUI({      user,      weather: weather.query.results    })  }, showError)}, showError)// Promises ✅getUser("tylermcginnis")  .then(getWeather)  .then((data) => updateUI(data))  .catch(showError);

Following that logic feels natural because it's how we're used to thinking, sequentially. getUser then getWeather then update the UI with the data.

遵循这种逻辑感觉很自然,因为这是我们习惯于顺序思考的方式。 getUser then getWeather then update the UI with the data



Now it's clear that promises drastically increase the readability of our asynchronous code, but is there a way we can make it even better? Assume that you were on the TC39 committee and you had all the power to add new features to the JavaScript language. What steps, if any, would you take to improve this code?

现在很明显,promise可以大大提高异步代码的可读性,但是有没有办法使它更好呢? 假设您是TC39委员会的成员,并且拥有向JavaScript语言添加新功能的全部权力。 您将采取哪些步骤(如果有)来改进此代码?

$("#btn").on("click", () => {  getUser("tylermcginnis")    .then(getWeather)    .then((data) => updateUI(data))    .catch(showError)})

As we've discussed, the code reads pretty nicely. Just as our brains work, it's in a sequential order. One issue that we did run into was that we needed to thread the data (users) from the first async request all the way through to the last .then. This wasn't a big deal, but it made us change up our getWeather function to also pass along users. What if we just wrote our asynchronous code the same way which we write our synchronous code? If we did, that problem would go away entirely and it would still read sequentially. Here's an idea.

正如我们所讨论的那样,代码读起来很不错。 正如我们的大脑在工作一样,它是按顺序排列的。 我们遇到的一个问题是,我们需要将数据( users )从第一个异步请求一直贯穿到最后一个.then 。 没什么大不了的,但是这使我们更改了getWeather函数以传递users 。 如果我们只是以编写同步代码的方式编写异步代码怎么办? 如果这样做,该问题将完全消失,并且仍会顺序读取。 这是个主意。

$("#btn").on("click", () => {  const user = getUser('tylermcginnis')  const weather = getWeather(user)  updateUI({    user,    weather,  })})

Well, that would be nice. Our asynchronous code looks exactly like our synchronous code. There's no extra steps our brain needs to take because we're already very familiar with this way of thinking. Sadly, this obviously won't work. As you know, if we were to run the code above, user and weather would both just be promises since that's what getUser and getWeather return. But remember, we're on TC39. We have all the power to add any feature to the language we want. As is, this code would be really tricky to make work. We'd have to somehow teach the JavaScript engine to know the difference between asynchronous function invocations and regular, synchronous function invocations on the fly. Let's add a few keywords to our code to make it easier on the engine.

好吧,那太好了。 我们的异步代码看起来与我们的同步代码完全一样。 我们的大脑不需要采取任何额外的步骤,因为我们已经非常熟悉这种思维方式。 可悲的是,这显然行不通。 如您所知,如果我们运行上面的代码,则userweather都只是一个承诺,因为那是getUsergetWeather返回的结果。 但是请记住,我们正在使用TC39。 我们拥有向所需语言添加任何功能的全部能力。 照原样,此代码确实很难上班。 我们必须以某种方式教会JavaScript引擎以了解异步函数调用与动态常规,同步函数调用之间的区别。 让我们在代码中添加一些关键字,以使其在引擎上更容易。

First, let's add a keyword to the main function itself. This could clue the engine to the fact that inside of this function, we're going to have some asynchronous function invocations. Let's use async for this.

首先,让我们向main函数本身添加一个关键字。 这可能使引擎了解到以下事实:在此函数内部,我们将进行一些异步函数调用。 让我们为此使用async

$("#btn").on("click", async () => {  const user = getUser('tylermcginnis')  const weather = getWeather(user)  updateUI({    user,    weather,  })})

Cool. That seems reasonable. Next let's add another keyword to let the engine know exactly when a function being invoked is asynchronous and is going to return a promise. Let's use await. As in, "Hey engine. This function is asynchronous and returns a promise. Instead of continuing on like you typically do, go ahead and 'await' the eventual value of the promise and return it before continuing". With both of our new async and await keywords in play, our new code will look like this.

凉。 这似乎是合理的。 接下来,让我们添加另一个关键字,以使引擎确切地知道所调用的函数何时是异步的并且将返回一个Promise。 让我们使用await 。 就像“嘿引擎。此函数是异步的,并返回一个诺言。与其像通常那样继续执行,不如继续并'等待'诺言的最终值,然后在继续之前返回它。” 有了我们新的asyncawait关键字,我们的新代码将如下所示。

$("#btn").on("click", async () => {  const user = await getUser('tylermcginnis')  const weather = await getWeather(user.location)  updateUI({    user,    weather,  })})

Pretty slick. We've invented a reasonable way to have our asynchronous code look and behave as if it were synchronous. Now the next step is to actually convince someone on TC39 that this is a good idea. Lucky for us, as you probably guessed by now, we don't need to do any convincing because this feature is already part of JavaScript and it's called Async/Await.

很漂亮 我们已经发明了一种合理的方法来使异步代码的外观和行为看起来像是同步的。 现在,下一步是实际上使TC39上的某人相信这是一个好主意。 幸运的是,您可能现在已经猜到了,我们不需要做任何令人信服的事情,因为此功能已经是JavaScript的一部分,并且被称为Async/Await

Don't believe me? that we've added Async/Await to it. Feel free to play around with it.

不相信我吗 ,我们已经添加了异步/等待, 。 随便玩吧。



异步函数返回一个承诺 (async functions return a promise)

Now that you've seen the benefit of Async/Await, let's discuss some smaller details that are important to know. First, anytime you add async to a function, that function is going to implicitly return a promise.

既然您已经了解了Async / Await的好处,那么让我们讨论一些重要的小细节。 首先,每当您向函数添加async ,该函数将隐式返回一个Promise。

async function getPromise(){}const promise = getPromise()

Even though getPromise is literally empty, it'll still return a promise since it was an async function.

即使getPromise实际上是空的,由于它是一个async函数,它仍然会返回一个getPromise

If the async function returns a value, that value will also get wrapped in a promise. That means you'll have to use .then to access it.

如果async函数返回一个值,则该值也将被包装在promise中。 这意味着您必须使用.then来访问它。

async function add (x, y) {  return x + y}add(2,3).then((result) => {  console.log(result) // 5})


没有异步等待是不好的 (await without async is bad)

If you try to use the await keyword inside of a function that isn't async, you'll get an error.

如果您尝试在非async功能中使用await关键字,则会收到错误消息。

$("#btn").on("click", () => {  const user = await getUser('tylermcginnis') // SyntaxError: await is a reserved word  const weather = await getWeather(user.location) // SyntaxError: await is a reserved word  updateUI({    user,    weather,  })})

Here's how I think about it. When you add async to a function it does two things. It makes it so the function itself returns (or wraps what gets returned in) a promise and makes it so you can use await inside of it.

这就是我的想法。 当您将async添加到函数时,它会做两件事。 它使函数本身返回(或包装返回的内容)一个promise,并使它可以在其中使用await



错误处理 (Error Handling)

You may have noticed we cheated a little bit. In our original code we had a way to catch any errors using .catch. When we switched to Async/Await, we removed that code. With Async/Await, the most common approach is to wrap your code in a try/catch block to be able to catch the error.

您可能已经注意到我们被骗了一点。 在我们的原始代码中,我们有一种方法可以使用.catch捕获任何错误。 当我们切换到异步/等待时,我们删除了该代码。 使用Async / Await,最常见的方法是将代码包装在try/catch块中,以便能够捕获错误。

$("#btn").on("click", async () => {  try {    const user = await getUser('tylermcginnis')    const weather = await getWeather(user.location)    updateUI({      user,      weather,    })  } catch (e) {    showError(e)  }})

这是我的
如果您喜欢这篇文章,请查看。 (This is part of my .
If you enjoy this post, check it out.)

翻译自:

转载地址:http://ipuzd.baihongyu.com/

你可能感兴趣的文章
android EditText自动弹出和自动关闭软键盘
查看>>
吉特日化MES-工业生产盲区
查看>>
Codeforces 517 #B
查看>>
实验四
查看>>
Scramble String
查看>>
php之接口概念
查看>>
01、计算机原理结构,及冯诺依曼体系结构
查看>>
Python 列表基本操作
查看>>
Linux TC基于CBQ队列的流量管理范例
查看>>
Python hashlib and hmac
查看>>
Fitnesse Page 简单使用
查看>>
C#.net 创建XML
查看>>
1057 数零壹
查看>>
隐马尔科夫模型(上)
查看>>
asp.net mvc FluentValidation 的使用
查看>>
java JvM
查看>>
HDU 1009 Just a Hook
查看>>
python基础之数据类型
查看>>
CSS居中初探
查看>>
element-ui table 点击分页table滚动到顶部
查看>>