5

异步 JavaScript – 回调、Promises和 Async/Await 解释

 1 year ago
source link: https://blog.p2hp.com/archives/9832
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如果您已经学习 JavaScript 一段时间了,那么您之前可能听说过“异步”这个术语。

这是因为 JavaScript 是一种异步语言……但这到底意味着什么?在本文中,我希望向您展示这个概念并不像听起来那么困难。

同步与异步

在我们进入真正的交易之前,让我们看一下这两个词——同步和异步。

默认情况下,JavaScript 是一种同步的单线程编程语言。这意味着指令只能一个接一个地运行,而不是并行运行。考虑下面的小代码片段:

let a = 1;
let b = 2;
let sum = a + b;
console.log(sum);

上面的代码非常简单——它将两个数字相加,然后将总和记录到浏览器控制台。解释器按这个顺序一个接一个地执行这些指令,直到它完成。

但是这种方法也有缺点。假设我们想从数据库中获取大量数据,然后将其显示在我们的界面上。当解释器到达获取此数据的指令时,其余代码将被阻止执行,直到数据被获取并返回。

现在您可能会说要获取的数据并没有那么大,并且不会花费任何明显的时间。想象一下,您必须在多个不同点获取数据。这种延迟复合听起来不像是用户想要遇到的事情。

对我们来说幸运的是,通过引入异步 JavaScript 解决了同步 JavaScript 的问题。

将异步代码视为可以现在开始并稍后完成执行的代码。当 JavaScript 异步运行时,指令不一定像我们之前看到的那样一个接一个地执行。

为了正确实现这种异步行为,开发人员多年来使用了一些不同的解决方案。每个解决方案都对前一个解决方案进行了改进,这使得代码更加优化并且在变得复杂的情况下更易于理解。

为了进一步了解 JavaScript 的异步特性,我们将了解回调函数、promise 以及 async 和 await。

JavaScript 中的回调是什么?

回调是在另一个函数内部传递的函数,然后在该函数中调用以执行任务。

令人困惑?让我们通过实际实现它来分解它。

console.log('fired first');
console.log('fired second');

setTimeout(()=>{
    console.log('fired third');
},2000);

console.log('fired last');

上面的代码片段是一个将内容记录到控制台的小程序。但这里有一些新东西。解释器将执行第一条指令,然后执行第二条,但它会跳过第三条并执行最后一条。

setTimeout是一个带有两个参数的 JavaScript 函数。第一个参数是另一个函数,第二个参数是该函数应该执行的时间(以毫秒为单位)。现在您看到了回调的定义开始发挥作用。

本例中的函数setTimeout需要在两秒(2000 毫秒)后运行。想象一下,它被带到浏览器的某个单独部分执行,而其他指令继续执行。两秒后,返回函数的结果。

这就是为什么如果我们在程序中运行上面的代码片段,我们会得到:

fired first
fired second
fired last
fired third

您会看到在函数setTimeout返回结果之前记录了最后一条指令。假设我们使用此方法从数据库中获取数据。当用户在等待数据库调用返回结果时,执行中的流程不会被中断。

这种方法非常有效,但仅限于某一点。有时,开发人员必须在其代码中多次调用不同的源。为了进行这些调用,回调被嵌套,直到它们变得非常难以阅读或维护。这被称为回调地狱

为了解决这个问题,引入了 Promise。

JavaScript 中的 Promise 是什么?

我们总是听到人们做出承诺。你那个承诺给你免费钱的表弟,一个承诺在未经允许的情况下不再碰饼干罐的孩子......但是 JavaScript 中的承诺略有不同。

在我们的上下文中,承诺是需要一些时间才能完成的事情。一个承诺有两种可能的结果:

  • 我们要么运行并解决承诺,要么
  • 沿线发生了一些错误,并且承诺被拒绝

Promise 的出现是为了解决回调函数的问题。Promise 接受两个函数作为参数。即,resolvereject。请记住,resolve 表示成功,reject 表示错误发生时。

让我们看一下工作中的 Promise:

const getData = (dataEndpoint) => {
   return new Promise ((resolve, reject) => {
     //some request to the endpoint;
     
     if(request is successful){
       //do something;
       resolve();
     }
     else if(there is an error){
       reject();
     }
   
   });
};

上面的代码是一个承诺,包含在对某个端点的请求中。就像我之前提到的那样resolve,承诺接受了。reject

例如,在对端点进行调用后,如果请求成功,我们将解决承诺并继续对响应做任何我们想做的事情。但是如果出现错误,promise 将被拒绝。

Promise 是一种解决回调地狱带来的问题的巧妙方法,采用一种称为Promise Chaining的方法。您可以使用此方法从多个端点顺序获取数据,但代码更少,方法更简单。

但是还有更好的方法!您可能熟悉以下方法,因为它是在 JavaScript 中处理数据和 API 调用的首选方式。

JavaScript 中的异步和等待是什么?

问题是,像回调一样将 Promise 链接在一起会变得非常庞大和混乱。这就是产生 Async 和 Await 的原因。

要定义异步函数,请执行以下操作:

const asyncFunc = async() => {

}

请注意,调用异步函数将始终返回一个 Promise。看看这个:

const test = asyncFunc();
console.log(test);

在浏览器控制台中运行上面的代码,我们看到asyncFunc返回了一个 Promise。

现在让我们真正分解一些代码。考虑下面的小片段:

const asyncFunc = async () => {
	const response = await fetch(resource);
   	const data = await response.json();
}

正如我上面提到的async,关键字是我们用来定义异步函数的。但是怎么样await?好吧,它会阻止 JavaScript 分配fetch给响应变量,直到 promise 被解决。一旦 promise 被解决,现在可以将 fetch 方法的结果分配给响应变量。

同样的事情发生在第 3 行。该.json方法返回一个承诺,我们可以使用await仍然延迟分配,直到承诺解决。

阻止代码或不阻止代码

当我说“拖延”时,您一定认为实现 Async 和 Await 会以某种方式阻止代码执行。因为如果我们的请求花费的时间太长了,对吧?

事实是,它没有。异步函数内部的代码是阻塞的,但这不会以任何方式影响程序的执行。我们代码的执行与以往一样是异步的。为了展示这一点,

const asyncFunc = async () => {
	const response = await fetch(resource);
   	const data = await response.json();
}

console.log(1);
cosole.log(2);

asyncFunc().then(data => console.log(data));

console.log(3);
console.log(4);

在我们的浏览器控制台中,上面的输出看起来像这样:

1
2
3
4
data returned by asyncFunc

您会看到,当我们调用asyncFunc时,我们的代码会继续运行,直到该函数返回结果。

本文不会深入探讨这些概念,但我希望它能向您展示异步 JavaScript 的含义以及需要注意的一些事项。

它是 JavaScript 的一个非常重要的部分,本文只是触及了皮毛。尽管如此,我希望这篇文章有助于打破这些概念。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK