如果你已经学习了一段时间的JavaScript,那么你可能已经听到了 "异步 "这个词。
这是因为JavaScript是一种异步语言......但这到底是什么意思?在这篇文章中,我希望告诉你,这个概念并不像它听起来那么困难。
同步与异步
在我们跳入真正的交易之前,让我们看看这两个词--同步和异步。
默认情况下,JavaScript是一种同步的、单线程的编程语言。这意味着,指令只能一个接一个地运行,而不能并行。考虑一下下面的小代码片断:
let a = 1;
let b = 2;
let sum = a + b;
console.log(sum);
上面的代码非常简单--它对两个数字进行求和,然后将和记录到浏览器的控制台。解释器按照这个顺序一个接一个地执行这些指令,直到完成。
但这种方法也有缺点。假设我们想从数据库中获取一些大量的数据,然后在我们的界面上显示这些数据。当解释器到达获取这些数据的指令时,其余的代码被阻止执行,直到数据被获取并返回。
现在你可能会说,要获取的数据并不大,不会花费任何明显的时间。想象一下,你必须在多个不同点上获取数据。这种延迟加起来,听起来不像是用户想遇到的事情。
对我们来说,幸运的是,同步JavaScript的问题通过引入异步JavaScript得到了解决。
把异步代码看作是可以现在开始,以后完成执行的代码。当JavaScript以异步方式运行时,指令不一定像我们之前看到的那样一个接一个地执行。
为了正确地实现这种异步行为,多年来开发人员使用了一些不同的解决方案。每个解决方案都是在前一个解决方案的基础上改进的,这使得代码更加优化,在变得复杂的情况下更容易理解。
为了进一步了解JavaScript的异步性,我们将通过回调函数、承诺、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 中的函数返回其结果之前,最后一条指令被记录下来了。假设我们用这个方法从数据库中获取数据。当用户在等待数据库调用返回结果时,执行中的流程不会被打断。
这种方法非常有效,但只是在一定程度上。有时,开发人员必须在他们的代码中对不同的来源进行多次调用。为了进行这些调用,回调被嵌套,直到它们变得非常难以阅读或维护。这被称为回调地狱
为了解决这个问题,人们引入了承诺。
什么是JavaScript中的承诺?
我们经常听到人们许下承诺。你的那个表哥承诺给你寄钱,一个孩子承诺未经允许不再碰饼干罐......但是JavaScript中的承诺略有不同。
在我们的语境中,一个承诺是需要花费一些时间来完成的事情。一个承诺有两种可能的结果:
- 我们要么运行并解决这个承诺,要么
- 沿线发生一些错误,承诺被拒绝。
承诺的出现是为了解决回调函数的问题。一个承诺接收两个函数作为参数。也就是说,resolve 和reject 。记住,resolve是成功的,reject是用于发生错误时。
让我们来看看诺言的工作情况:
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 。
例如,在对端点进行调用后,如果请求成功,我们将解析承诺并继续对响应进行任何操作。但如果出现错误,承诺就会被拒绝。
承诺是一种解决回调地狱带来的问题的巧妙方法,这种方法被称为承诺链。你可以用这种方法从多个端点依次获取数据,但代码更少,方法更简单。
但是,还有一个更好的方法你可能对以下方法很熟悉,因为它是JavaScript中处理数据和API调用的首选方式。
什么是JavaScript中的Async和Await?
问题是,像回调一样把承诺链在一起,会变得非常笨重和混乱。这就是Async和Await出现的原因。
要定义一个异步函数,你要这样做:
const asyncFunc = async() => {
}
注意,调用一个异步函数总是会返回一个Promise。看一下这个:
const test = asyncFunc();
console.log(test);
在浏览器控制台中运行上述内容,我们看到asyncFunc ,返回一个承诺。
现在让我们真正分解一些代码。考虑一下下面的小片段:
const asyncFunc = async () => {
const response = await fetch(resource);
const data = await response.json();
}
如上所述,async 关键字是我们用来定义异步函数的。但是await 呢?嗯,它使JavaScript无法将fetch 赋值给响应变量,直到承诺被解决。一旦承诺被解决,从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的一个非常重要的部分,而这篇文章只涉及到了表面。尽管如此,我希望这篇文章有助于打破这些概念。