如果您已经学习 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 的一个非常重要的部分,本文只是触及了皮毛。尽管如此,我希望这篇文章有助于打破这些概念。

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