前言
在JavaScript的学习过程中,异步、回调函数、Promise是绕不开的几个词。为方便读者和自己加深理解,本篇文章将其用通俗易懂的方式梳理一下,新手向。可能你也有相同的困惑,或者你已经学习到某一步,亦或者只是为了无聊想浪费点时间,这篇应该都能帮到你。
目录
一、异步(Asynchronous)
异步和同步的概念通常一起出现来对比。大一的时候老师就教给我们C语言是逐行执行的,于是我们常浅显的认为代码都是逐行执行的,只到遇到JS。JavaScript代码是异步执行的。
怎么理解?节约时间,先上代码:
// 理解JS异步
var a = 0
function fun1() {
// 耗时的操作
setTimeout(() => {
a = 1
console.log('fun1被执行了')
}, 3000);
}
function fun2(x) {
console.log(x)
}
fun1() // 看似先执行
fun2(a) // 实际先执行
代码段很简单,梳理一下:先在外层定义了一个变量a,值为0;定义了两个函数:fun1设置了一个定时器,3s后将a的值修改为1,并输出一段话;fun2将传入的参数值输出。于是到了执行部分:
fun1() // 看似先执行
fun2(a) // 实际先执行
看起来,是fun1先执行,将a值修改,然后fun2执行,将传入的a输出。但实际上,输出结果为:
0
fun1被执行了
与我们期待的不同?解释一下:JS先看到了fun1(),进入后发现他是一个要耗时3s的计时器,于是他就进入了下一行fun2,发现他只是简单的输出,迅速就可执行完毕;所以JS采取了先执行省事儿的fun2,后执行了耗时的fun1。结论:JS是一个聪明的语言,它不会等待某个耗时的代码段执行完毕才执行后面的代码。也就一定程度上避免了因为某段代码而卡死。但他与我们常规的理解不同,会带来很多的麻烦,但没关系仍有解决方案:回调函数。
二、回调函数(callback)
异步有好处有坏处,为解决上述提到的异步问题,早些时候(现在仍然有很多)我们会采用回调函数方式解决。先上代码:
// 理解JS callback
var a = 0
function fun1(callback) {
// 耗时的操作
setTimeout(() => {
a = 1
callback(a) // 没有函数名,所以叫回调函数
console.log('fun1被执行了')
}, 3000);
}
function fun2(x) {
console.log(x)
}
fun1(fun2)
为了对比,采取了与上段相同的代码结构。不同的地方在于:在fun1中使用了callback关键字。直接这样理解:在fun1传入了一个参数,这个参数是一个函数。在fun1中继续修改了a的值,然后执行了函数callback,在callback中传入了a(此处我们先不要callback是做什么,就当他是某个函数就好)。接着还是定义了fun2。执行的时候我们将fun2传入到了fun1的参数中,相当于在fun1中执行了fun2。这样便是我们预想到的执行顺序了。
我们来看一下输出,等待三秒之后:
1
fun1被执行了
简单讲,回调函数就是给我们一个异步的解决方案,callback可以指代任何函数。比如执行完某项函数后我们想进行下一步操作,又担心js会不会影响代码的结果和我们预想不同,就可以采取这种方式。但弊端也存在,回调回调回调回调叠加起来,代码会特别难阅读和维护,也就是常称为的:回调陷进(callback loop trap)。于是又引出了我们的Promise。
三、Promise
Promise(承诺)的三种状态:pending(进行中),resolved(解决),rejected(拒绝)。通过字面意思我们就可以理解有两种结果,即解决了,和拒绝了。带入上面的情景:JS承诺在函数1执行完后执行函数2,那么成功的话就变为resolved。如果失败的话就变成了rejected。
我们这样理解:Promise就是JS来帮助你写回调函数的对象!巧的是Promise仍可以返回一个Promise对象。那么是不是就可以解决刚刚提到的回调陷进了呢!
抽象的话就直接上代码!当然,要有一些ES6的基础(如箭头函数),不了解的伙伴可以先用callback解决大部分需求!
var a = 0
const p = new Promise((resolve, reject) => {
setTimeout(() => {
a = 1
resolve(a)
}, 2000);
})
p.then((data) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('此时的a值:', data)
a = 2
resolve(a)
}, 2000);
})
}).then((param) => {
console.log('最后的a值:', param)
})
同样的,创建了一个变量a初始值为0;然后我们利用构造函数构造了一个promise对象,要传入两个关键字,resolve和reject。(这里为了简单,省去了reject的过程,各位可以理解为是抛出错误的接口)。resolve也是一个函数,方便我们将函数处理的值传入下一个对象。然后利用then关键字取出刚刚resolve传入的值。为了增加难度,在第二次回调时我们将a此时的值输出,并且又重新对a进行赋值,再一次resolve给下一个回调。最后一次回调,我们将resolve传递的参数输出。(在这里给小伙伴们指条路,如果先有Ajax或Axios的经验,会加深理解哦)
我们看一下输出,是不是我们想要的:
此时的a值: 1
最后的a值: 2
大功告成!还有些晕?看总结!
总结
在大多数的编程语言中,函数的形参往往是从外部向内部传入的,或是另一段代码块中的变量,或是外部的变量。但在回调函数中,它表示函数体在完成某种操作后由内向外调用某个外部函数。回调函数就很好的解决了JS的异步问题;Promise则是JS采取更直观,更易理解的方式对回调进行表达!你可以在函数中返回任意你想要的值,然后利用返回值进行你想要的操作。
希望能帮到大家~