如果您已经学习 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 接受两个函数作为参数。即,resolve
和reject
。请记住,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 的一个非常重要的部分,本文只是触及了皮毛。尽管如此,我希望这篇文章有助于打破这些概念。