前言
async await是一种用于编写异步代码的编程技术,它简化了异步操作的处理,使代码看起来和表现得更像同步代码。
回调函数
在讲解async await之前我们先来了解一下什么是回调函数!
回调函数是一种常见的编程模式,特别是在异步编程中。它是一种函数,作为参数传递给另一个函数,并且在某个特定事件发生或条件满足时被调用。
我们来看一段代码:
js
function a(){
setTimeout(() => {
console.log("A");
},1000)
}
function b(){
setTimeout(()=>{
console.log("B")
})
}
我们可以看出结果为BA,接下来我们可以思考一下,A定义在B之前,为什么输出的结果是BA而不是AB呢? 因为我们在A中加了计时器,在1s后执行console.log语句,B不用计时输出,所以B先输出,A再输出。
当我们想输出AB结果时,我们就可以采用回调函数,看如下代码(对上一段代码的修改)
js
function a(callback){ //我们在函数a的定义中传了一个形参callback
setTimeout(() => {
console.log("A");
callback() //调用了callback(),也就是b()
},1000)
}
function b(){
setTimeout(()=>{
console.log("B")
})
}
a(b) //接收一个实参b
这就是加入回调函数之后的代码,这样即使是A需要计时等待,也会输出按AB顺序输出。
回调函数的优缺点
回调函数作为一种处理异步操作的模式,具有一些优点和缺点:
优点:
- 灵活性: 回调函数可以让你以一种非阻塞的方式处理异步操作,允许你在操作完成后执行一些特定的逻辑。这种灵活性使得回调函数在处理各种异步场景时非常有用。
- 简单直观: 回调函数通常是编写的最基本的异步处理方式之一。对于简单的异步操作,使用回调函数可以保持代码的简洁和直观。
- 广泛支持: 回调函数在几乎所有的编程语言和框架中都有支持,因此它是一种通用且可靠的异步编程模式。
缺点:
- 回调地狱(Callback Hell):当你有多个嵌套的回调函数时,代码可能会变得难以阅读和维护,这就是所谓的回调地狱。这种情况下,代码会变得深度嵌套,导致可读性差、调试困难和代码重复。
- 错误处理困难: 在一些复杂的异步操作中,错误处理可能变得复杂。如果不小心处理错误,可能会导致错误被忽略或无法正确处理。
- 难以捕获和传递数据: 在一些情况下,回调函数并不总是方便地处理数据的传递和捕获。特别是在多个回调函数之间需要共享数据时,可能需要引入额外的逻辑来处理数据的传递和共享。
- 可读性差: 对于复杂的异步操作,使用回调函数可能会导致代码变得难以理解和维护,因为逻辑分散在多个回调函数中。
现代JavaScript中出现了许多替代方案,例如Promise、async/await等,以解决回调函数带来的一些问题,提高代码的可读性、可维护性和错误处理能力。
promise
由于回调函数的弊端,使得promise以及async await的出现,提高了代码的可读性。
下面利用一段代码来讲解一下,promise是如何使用的:
js
function a(){
return new Promise((resolve,reject)=>{ //return一个实例对象Promise
setTimeout(() => {
console.log("A");
resolve() //resolve()调用时.then才会生效,回调函数中的b()才会调用
},1000)
})
}
function b(){
console.log("B")
}
a().then(()=>{ //在a()的调用后加 .then(),then是实例对象promise原型上自带的方法
b()
})
我们首先return一个实例对象Promise,在Promise的回调函数中接收两个形参(resolve,reject),将计时器放入回调函数中,并调用resolve(),当resolve()执行时,结尾的a().then才会执行,其中then回调函数中的b()才会执行,此时"a"以及输出了,"b"才会输出。
此外resolve()中允许传一个实参,在.then回调函数中接收并可以输出:
js
function a(){
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log("A");
resolve("中国") //实参:中国
},1000)
})
}
function b(){
console.log("B")
}
a().then((resolve)=>{ //回调函数中拿到resolve的值
console.log(resolve) //输出中国
b()
})
.then的链式调用
看如下代码,连续输出ABC三个字母
js
function a(){
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log("A"); //输出A
resolve()
},1000)
})
}
function b(){
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log("B"); //输出B
resolve()
},500)
})
}
function c(){
console.log("C"); //输出C
}
a()
.then(()=>{ //链式调用
return b() //return 出b()中的promise,使得后续能再调用.then输出C
})
.then(()=>{
c()
})
首先我们调用a(),当执行到a()中的resolve()时,a().then开始生效,其中回调函数中的b()开始生效,输出b,并且return出b()中的promise,使得调用后续的.then输出c,所以输出的顺序时ABC。
如果没有return 出b中的promise,则打印出的结果将会是ACB,因为a().then会自动返回一个promise接收第二个.then,而不是b中的promise接收后续的.then ,我们就不能通过调用resolve()来使得输出B在输出C前,而当我们return b()时,因为b()中return了promise,此个promise就会覆盖掉a().then自动返回的promise,使得后续的.then会在b()return的promise上,就可以先输出B再调用resolve()输出C。
async await
在JavaScripta 中,"async"是"异步"的简写用于声明一个函数是异步的。当一个函数被声明为async时,它可以在函数体内使用"await关键字来等待一个异步操作完成。而"await"关键字用于等待-个Promise对象resolve,并可以将resolve的值赋给一个变量,或者传递给下一个异步操作。
就拿上一段代码来说:
js
function a(){
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log("A");
resolve()
},1000)
})
}
function b(){
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log("B");
resolve()
},500)
})
}
function c(){
console.log("C");
}
async function foo(){ //此处使用async声明函数
await a() //await等待异步操作的完成,即resolve()的执行
await b()
c()
}
foo()
我们将最后调用阶段的代码使用async await技术,使得我们的代码更加简便,await就相当于.then,await在等待a()和b()中的Promise,若a()和b()中没有return promise,输出则相反CBA。
从回调函数------>promise------>async await,使我们书写代码变得更加简便,代码的可读性也更强!