可视化的承诺和异步/等待



朋友们,美好的一天!

我向您介绍Lydia Hallie 撰写的文章“可视化的JavaScript:承诺与异步/等待”

您是否遇到过JavaScript代码无法正常工作?以任意,不可预测的顺序执行功能或延迟执行功能时。许诺的主要任务之一是简化功能的执行。

我永不满足的好奇心和不眠之夜获得了丰厚的回报-感谢他们,我制作了一些动画。现在该谈论承诺了:它们如何工作,为什么要使用它们以及如何实现。

介绍


在编写JS代码时,我们经常不得不处理依赖于其他任务的任务。假设我们要获取图像,对其进行压缩,对其应用滤镜并保存。

首先,我们需要获取图像。为此,请使用功能getImage。加载图像后,我们将其传递给函数resizeImage。压缩图像后,我们使用函数对它应用滤镜applyFilter。压缩并应用滤镜后,我们将保存图像并通知用户成功。

结果,我们得到以下结果:



嗯...你有没有注意到?尽管一切正常,但这并不是最佳方法。我们得到了许多嵌套的回调函数,这些函数取决于以前的回调。这被称为回调地狱,这使得阅读和维护代码非常困难。

幸运的是,今天我们有诺言。



承诺语法


在ES6中引入了承诺。在许多手册中,您可以阅读以下内容:

承诺(promise)是将来实现或拒绝的价值。

是的。一般的解释。一次,它使我认为诺言是一种奇怪,模糊,某种魔术。他们到底是什么?

我们可以使用Promise将回调函数作为参数的构造函数来创建Promise。酷,让我们尝试:



等一下,这又是什么?

Promise是包含状态([[PromiseStatus]])和值([[PromiseValue]])的对象。在上面的例子中,值[[PromiseStatus]]pending,和承诺的值undefined

不用担心,您不必与该对象进行交互,甚至都不能访问[[PromiseStatus]]属性[[PromiseValue]]但是,这些属性在兑现承诺时非常重要。

PromiseStatus 或承诺的状态可以采用以下三个值之一:

  • fulfilled: resolved (). ,
  • rejected: rejected (). -
  • pending: , pending (, )

听起来不错,但是一个承诺何时会获得所指示的状态?地位为什么重要?

在上面的示例中,我们将一个Promise简单的回调函数传递给构造函数() => {}。该函数实际上有两个参数。第一个参数的值通常称为resolveres,是执行promise时调用的方法。第二个参数的值通常称为rejectrej,是在出问题时拒绝promise时调用的方法。



让我们看看调用方法resolve时输出到控制台的内容reject



酷!现在我们知道了如何摆脱地位pending和意义undefined。调用方法时的承诺的情况resolvefulfilled,当reject-rejected

[[PromiseValue]]或promise的值是我们传递给方法resolvereject作为参数的值。

有趣的事实:杰克·阿奇博尔德(Jake Archibald)在阅读了本文之后,指出了Chrome中的一个错误,该错误fulfilled返回resolved



好的,现在我们知道如何使用该对象了Promise。但是它是做什么用的呢?

在引言中,我给出了一个示例,在该示例中,我们获取图像,对其进行压缩,对其应用滤镜并保存。然后,一切都以回调地狱结束。

幸运的是,诺言可以帮助解决这一问题。我们重写代码,以便每个函数返回一个Promise。

如果图像已加载,我们将履行承诺。否则,如果发生错误,请拒绝承诺:



让我们看看在终端中运行以下代码会发生什么:



酷!如我们所料,Promis返回带有已解析(“已解析”)数据的数据。

但是...接下来是什么?我们对承诺的主题不感兴趣,我们对它的数据感兴趣。有3种内置方法来获取Promise值:

  • .then():履行承诺后致电
  • .catch():在兑现承诺后被称为
  • .finally():总是在执行和拒绝承诺后调用



方法.then采用传递给方法的值resolve



方法.catch采用传递给方法的值reject



最后,我们得到了所需的值。我们可以用这个值做任何事情。

当我们在履行或拒绝承诺的信心,你可以写Promise.resolve任何Promise.reject恰当的值。



在以下示例中将使用此语法。



结果.then是Promise的值(即此方法也返回Promise)。这意味着我们可以.then根据需要使用:前一个的结果.then作为参数传递给next .then



在其中getImage我们可以使用多个.then将处理后的图像传输到下一个函数。



这种语法看起来比嵌套回调函数的阶梯好得多。



微任务和(宏)任务


好了,现在我们知道了如何创建承诺以及如何从中提取价值。向我们的脚本中添加一些代码,然后再次运行它:



首先,它显示在控制台中Start!。这很正常,因为我们有第一行代码console.log('Start!')。控制台上显示的第二个值是End!,而不是已完成的Promise的值。最后显示诺言的值。为什么会发生?

在这里,我们看到了承诺的力量。尽管JS是单线程的,但我们可以使用来使代码异步Promise

我们还能在哪里观察到异步行为?浏览器中内置的某些方法(例如setTimeout)可以模拟异步。

在事件循环(Event Loop)中,有两种类型的队列:(宏)任务或仅任务的队列((宏)任务队列,任务队列)和微任务或仅微任务的队列(微任务队列,微任务)。

什么适用于每个人?简而言之,则:

  • 宏任务:setTimeout,setInterval,setImmediate
  • 微任务:process.nextTick,Promise回调,queueMicrotask

我们Promise在微任务列表中看到。Promise被执行并调用的方法then()catch()或者finally(),与该方法中的回调函数被添加到microtask队列。这意味着带有该方法的回调不会立即执行,这会使JS代码异步。

当是该方法then()catch()或者finally()是它执行?事件循环中的任务具有以下优先级:

  1. 首先,执行调用堆栈中的功能。这些函数返回的值将从堆栈中删除。
  2. 释放堆栈后,将放置并依次执行微任务(微任务可以返回其他微任务,从而形成无尽的微任务循环)。
  3. 释放堆栈和微任务队列后,事件循环将检查宏。宏任务被压入堆栈,执行并删除。



考虑一个例子:

  • Task1:例如通过调用代码立即添加到堆栈的函数。
  • Task2Task3Task4:Mikrozadachi例如thenPROMIS或任务通过加入queueMicrotask
  • Task5Task6:例如宏任务,setTimeoutsetImmediate



首先Task1返回一个值,然后将其从堆栈中删除。然后,引擎检查相应队列中的微任务。在从堆栈中添加并随后删除微任务之后,引擎会检查宏任务,这些宏任务也将添加到堆栈中,并在返回值后从堆栈中删除。

足够的话。让我们编写代码。



在这段代码中,我们有一个宏setTimeout任务和一个微任务.then。运行代码,然后查看控制台中显示的内容。

注意:在上面的示例中,我使用console.logsetTimeout方法Promise.resolve。所有这些方法都是内部方法,因此它们不会出现在堆栈跟踪中-当您在浏览器疑难解答工具中找不到它们时,不要感到惊讶。

在第一行中console.log。它被添加到堆栈中并显示在控制台中Start!。之后,从堆栈中删除此方法,引擎继续解析代码。



引擎到达setTimeout该引擎已添加到堆栈中。此方法是内置的浏览器方法:其回调函数(() => console.log('In timeout'))已添加到Web API,并且在计时器触发之前就存在。尽管计时器计数器为0,但回调仍然首先放置在WebAPI中,然后放置在宏任务队列中:setTimeout-这是一个宏任务。



接下来,引擎达到方法Promise.resolve()。将此方法添加到堆栈中,然后使用value执行Promise。他的回调then位于微任务队列中。



最后,引擎达到第二种方法console.log()。它立即被推入堆栈,并输出到控制台End!,从堆栈中删除该方法,然后引擎继续。



引擎“看到”堆栈为空。检查任务队列。它位于那里then将其推入堆栈,promise的值将显示在控制台上:在这种情况下,为string Promise!



引擎看到堆栈为空。他在微任务队列中“看起来”。她也空着。

是时候检查宏任务队列了:它在那里setTimeout它被压入堆栈并返回一个方法console.log()字符串输出到控制台'In timeout!'setTimeout从堆栈中删除。



做完了 现在一切都准备就绪了,对吧?



异步/等待


ES7引入了一种在JS中使用异步代码的新方法。使用关键字asyncawait我们可以创建一个隐式返回promise的异步函数。但是...我们该怎么做?

早些时候,我们讨论了如何明确地创建一个对象Promise:使用new Promise(() => {})Promise.resolvePromise.reject

相反,我们可以创建一个隐式返回指定对象的异步函数。这意味着我们不再需要手动创建Promise



异步函数隐式返回promise的事实固然很好,但是使用关键字时,该函数的功能得到了充分体现awaitawait使异步函数等待promise(其值)完成。要获得已履行承诺的价值,我们必须为变量分配承诺的预期(已等待)值。

原来,我们可以延迟异步函数的执行吗?很好,但是……这意味着什么?

让我们看看运行以下代码会发生什么:







起初引擎看到了console.log。该方法被压入堆栈并显示在控制台上Before function!



然后调用异步函数myFunc(),执行其代码。在此代码的第一行中,我们console.log用line 调用第二'In function!'。此方法已添加到堆栈中,其值显示在控制台中,并且已从堆栈中删除。



接下来执行功能代码。在第二行,我们有一个关键字await

在这里发生的第一件事是期望值的执行:在这种情况下,是function one。它被推入堆栈并返回承诺。诺言完成后,函数one返回了值,引擎将看到await

之后,异步功能的执行被延迟。功能主体的执行被挂起,其余代码作为微任务执行。



在异步函数的执行已延迟之后,引擎将在全局上下文中返回代码的执行。



在全局上下文中执行了所有代码之后,事件循环将检查微任务并检测myFunc()myFunc()推入堆栈并执行。

变量res获取函数返回的已执行promise的值one。我们console.log用变量的值调用resOne!在这种情况下为字符串One!它显示在控制台中。

做完了注意异步函数和thenpromis 方法之间的区别吗?关键词await延迟异步函数的执行。如果使用then,那么Promise主体将继续运行。



原来很冗长。兑现诺言时,如果您感到不安全,请不要担心。需要一些时间来适应他们。这是使用JS中的异步代码的所有技术所共有的。

另请参阅“服务职工工作的可视化

感谢您的时间。我希望它花得很好。

All Articles