程序员提升,逃不过的一个话题就是代码的执行效率,后端如此,前端也是如此,所以async/await异步开发就是大家提升自己代码效率的一个重要知识点,Python如此,JS亦如此,那么我们今天基于JS聊聊async/await异步编程。
异步
还是,先聊聊基础的概念,异步,他的反义词是同步,追究根源,要聊到计算机原理当中的进程和执行队列,简单来说就是在常规情况下,我们执行一个功能,在当前功能执行的过程当中,如果遇到文件内容加载或者是等待响应,那么程序就会进入阻塞状态,不会立马给出响应,也不会继续执行新的工作,那么基于前端发送邮件来举例:
(1)请求发送邮件
(2)邮件发送中
(3)页面等待邮件发送完成
(4)用户等待页面响应
(5)用户烦了,开始刷新页面重试
(6)重复上面步骤2-3次,用户骂骂咧咧或者叽叽歪歪的走了
(7)差评
所以基于上面的描述,可以看到阻塞不但影响代码执行的效率,对于用户体验来说也是非常致命的,那么,就有了异步的概念,所谓的异步就是在发起执行之后,程序进入等待,不进入阻塞状态,而是继续执行下面的功能,知道等待结束返回结果,再次进行响应,显而易见,他可以很好的避免上面的问题。基于前端,我最初了解的异步是ajax,异步请求,局部刷新页面,当时感觉相当nice,但是逐渐发现ajax只是对请求进行了异步,对需要异步的函数并没有太好的解决方式,那么这种情况下就有了async和await
async
作为一个关键字放在函数的前面,表示这个函数是一个异步函数,那么这个函数的执行不会阻塞后面代码的执行,但是异步函数的调用和普通函数是一样的,但是返回结果不一样。
<script>
async function say_hello() {
return "hello world"
}
console.log(say_hello()); //Promise 对象
console.log("hi man") //
</script>
可以看到say_hello被转换为异步函数之后,并没有直接返回结果,而是返回了一个Promise对象,通过调试模式,我们可以看到再promise上面由几个方法,根据单词的含义有点像生命周期。我们依次调用看看。
<script>
async function say_hello(f) {
if(f === 1){
return "hello world";
}else{
throw new Error("这里要发生错误"); //引发错误
}
}
say_hello().catch((args)=>{
console.log("catch 被调用了");
console.log(args);
console.log("catch 被调用了");
});
say_hello().finally(()=>{
console.log("finally 被调用了收尾");
});
say_hello().then((args)=>{
console.log("then 被调用了");
console.log(args);
console.log("then 被调用了");
});
console.log(say_hello(1));
</script>
constructor后面详细聊,那么下面的三个函数很明显可以看出是再不同状态下执行的
方法 | 描述 |
---|---|
catch | 有参数,在异步函数当中发生错误的时候执行,参数接收的是错误的内容 |
then | 有参数,在异步函数正常完成后执行,参数是异步函数返回的结果 |
finally | 在错误函数执行完成后执行,如没有发生作为,先于then执行 |
更加深入的聊就是:Promise对象有一个状态,如果Promise 对象状态变为resolved,调用then
方法指定的回调函数;如果异步函数抛出错误,状态就会变为rejected,调用catch
方法指定的回调函数,处理这个错误。而finally就是用来在错误后收尾的函数,而且要注意到是:then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
await
await即等待,用于等待一个Promise对象执行(有点协程的意思了啊)。它只能在异步函数 async function中使用,如果它等到的不是一个 promise 对象,那 await 表达式的运算结果就是它等到的东西。如果它等到的是一个 promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
<script>
async function say_hai(){
return "hi world"
}
async function say_hello() {
await say_hai().then((args)=>{
console.log("hi man")
}); //这里await右侧是一个异步函数,所以会阻塞程序,到这个异步函数执行完成之后,再往下执行
return "hello world"
}
say_hello().then((args)=>{
console.log("then 被调用了");
console.log(args);
console.log("then 被调用了");
});
console.log(say_hello());
</script>
<script>
function say_hai(){
return "hi world"
}
async function say_hello() {
const res = await say_hai(); //这里await右侧是一个常规函数,所以await只是拿到了函数返回的结果
console.log(res);
return "hello world"
}
say_hello().then((args)=>{
console.log("then 被调用了");
console.log(args);
console.log("then 被调用了");
});
console.log(say_hello());
</script>
所以await通常用在异步函数当中阻塞执行另外的一个异步函数,如果结合生产消费者,一个异步函数生产,一个异步函数消费,在生产的内部使用await调用消费就会产生生产一个,调用一个,并且在生产和调用函数执行过程当中进行来回切换的效果,执行效果更加地同步。
但是注意:
await 关键字会阻塞其后的代码,直到promise完成,就像执行同步操作一样,所以不恰当的使用过多的await,每个await都会等待前一个完成, 那么就失去了使用异步函数的意义。