1 async 和 await
首先,我们来回顾一下能够解决“回调地狱”的 Promise 对象及其用法。
<script>
new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('red')
}, 2000);
}).then((res)=>{
console.log(res);
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('yellow')
}, 1000);
})
}).then((res)=>{
console.log(res);
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('green')
}, 3000);
})
}).then((res)=>{
console.log(res);
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('ok1')
}, 2000);
})
}).then((res)=>{
console.log(res);
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('ok2')
}, 1000);
})
}).then((res)=>{
console.log(res);
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('ok3')
}, 3000);
})
}).then((res)=>{
console.log(res);
})
</script>
使用 Promise 对象消除“回调地狱”时利用了“链式编程”,这种方式虽然比回调嵌套更具有可读性和可维护性,但是长长的一连串代码仍然让人觉得有些头疼,和印象中的“结构化编程”有点搭不上边儿。
如果能让每个 new Promise() 都分开执行,变成单独的一或两行代码,那么在观感上就变成了我们非常熟悉的“结构化编程”的样子,我们只需要利用 async 和 await 就能做到这一点。
1.async 和 await 的基本使用
(1)async 用于修饰一个函数, 表示该函数为异步函数,
(2)await 为 async 函数内的关键字,它仅仅作为一个标记,表示其后紧接的代码为异步代码, 如果 async 函数内没有 await,也就是说其内部全是同步代码,则 async 无意义,而 await 也只能用在 async 函数中,单独使用会报错。
(3)await 后面一般会跟一个 Promise 对象,await 会阻塞 async 函数的执行,直到 Promise 成功执行后(resolve的结果)才会继续执行后续代码。
(4)await 只会等待 Promise 成功的结果,如果失败了则会报错(这种情况就需要使用 try catch)。
<script>
async function fn() {
try {
let { data: res } = await axios.get('http://www.xxx.top:3006/api/getbooks')
console.log(res) // axios请求成功则会打印请求结果
} catch (e) {
console.log(e) // axios请求失败则会打印错误消息
}
}
fn()
</script>
2.async 和 await 解决“回调地狱”问题
<script>
// 使用 async await 解决回调地狱问题
async function solveFn(){
await new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('red')
}, 2000);
}).then((res)=>{
console.log(res);
})
await new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('yellow')
}, 1000);
}).then((res)=>{
console.log(res);
})
await new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('green')
}, 3000);
}).then((res)=>{
console.log(res);
})
await new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('ok1')
}, 2000);
}).then((res)=>{
console.log(res);
})
await new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('ok2')
}, 1000);
}).then((res)=>{
console.log(res);
})
await new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('ok3')
}, 3000);
}).then((res)=>{
console.log(res);
})
}
solveFn()
</script>
与“链接编程”相比,上面的代码更符合“结构化编程”的习惯,而且还能达到同样的效果。
2 事件循环队列
由于 JavaScript 是一门单线程执行的编程语言,也就是同一时间只能做一件事情,因此会遇到假如前一个任务非常耗时,那么后续任务就不得不一直等待的情况。为了防止某个耗时任务导致程序假死的问题,JavaScript 会将异步代码委托给宿主环境(node环境, 浏览器)等待执行。
下面是一张任务执行图,
图中的宏任务是指诸如主代码块、setTimeout、setInterval之类的代码,它们都有一个共同的特点,就是上一个执行完毕后才会考虑执行下一个;微任务是指诸如 promise.then、promise.catch之类的代码,它们会在当前宏任务执行完毕,下一个宏任务开始前执行。