JS学习笔记——异步回调中Async Await和Promise区别

1、前提

js的同步和异步问题通常是指ajax的回调。

如果是同步调用,程序在发出ajax调用后就会暂停,直到远程服务器产生回应后才会继续运行;而如果是异步调用,程序发出ajax调用后不会暂停,而是立即执行后面的代码,服务器返回信息后会自动触发回调函数进行处理。

相比较而言,异步调用的性能最佳,程序不会出现卡顿的现象,而同步调用则通常用于需要立即获得结果并实时处理的情况。

2、正文

promise是ES6,async/await是ES7

异步编程的最高境界就是不关心它是否是异步。async、await很好的解决了这一点,将异步强行转换为同步处理。

async/await与promise不存在谁代替谁的说法,因为async/await是寄生于Promise,Generater的语法糖。

async await 它是基于promise的,为什么es7新增了这个?为了解决大量复杂不易读的Promise异步的问题才出现的。async/await相对于promise来讲,写法更加优雅

3、async

async必须声明的是一个function

async test= function () {
     return "我是个promise返回值" 
};
//语法错误  async必须声明的是一个function

async 是“异步”的意思,所以应该很好理解 async 用于申明一个 异步的function,返回的是一个Promise对象!

async function test() {
        return "我是个promise返回值"   // return Promise.resolve("我是个promise返回值")
      }
test();
// Promise {<fulfilled>: "我是个promise返回值"}

由上可以,返回的是一个promise,不过可以理解为一个状态为fulfilled,并且已经拿到了返回值为"我是个promise返回值"的promise ,或者可以理解为return Promise.resolve(“我是个promise返回值”),同理如果没有返回值也是Promise.resolve(underfind),下面我们来验证下,后面接个回调函数!

async function test() {
          return "我是个promise返回值"   // return Promise.resolve("我是个promise返回值")
      }
test().then(result=>console.log('111',result))
//  111 我是个promise返回值
//  Promise {<fulfilled>: undefined}

是这样的!那么对待async要像对待Promise一样去对待async的返回值。

4、await
4.1、await是干啥的

await必须是在这个async声明的函数内部使用,意思就是async和await是配对使用的,await存在于async的内部,必须是直系,否则会报错!

async function test() {
    function test1() {
        await 111;
    };
};
// Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

(为了验证,请看下面代码)

async function test() {
    await setTimeout(() => {
        console.log("我是不是最开始执行")
    }, 1000);
    console.log('我什么时候执行');
}
test();
// 我什么时候执行
//(1s后)我是不是最开始执行

我们发现,并不是像我们预期的那样,先打印‘我是不是最开始执行’,而是从下面先开始执行了?。
这里是有条件的,如果await后面是 promise对象,会将promise异步操作转为同步等待 promise的resolve / reject 返回结果,再接着执行函数体内后面的语句!
( 继续测试下)

async function test() {
    await new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("我是不是最开始执行")
        }, 1000);
    });
    console.log('我什么时候执行');
}
test();
// (1s后) 我是不是最开始执行

我们发现,这跟预期的还是不一样。最下面的 console.log(‘我什么时候执行’)没执行。
原来是如果await的是一个promise对象,那么要等待这个对象解析完成;
如果没有 resolve 或者 reject ,那么后面的内容就不会执行!
(我们进行修改,再测试一下)

async function test() {
    await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
            console.log("我是不是最开始执行")
        }, 1000);
    });
    console.log('我什么时候执行');
}
test(); 
// (1s后) 我是不是最开始执行    
// (1s后,和上面同时) 我什么时候执行
4.2、小结论

所以我们可以得出一个结论
(1)await后面如果不是一个promise对象,那么它就按正常的js顺序执行,先执行同步代码,当主线程空闲了,再去执行异步队列的任务!

(2)await后面如果是promise对象,那么await后面的内容还是相当于在then执行,跟promise的区别在于,如果等待的是一个promise对象,那么要等待这个对象解析完成,如果没有resolve或者reject,那么后面的内容就不会执行;如果有resolve或者reject,那么后面的内容正常执行

async function test() {
     console.log("async1");
     await new Promise((resolve, reject) => {
         resolve()
     });
     console.log('async2');
 };
 //等价于
 async function test() {
     console.log("async1");
     await new Promise((resolve, reject) => {
         resolve()
     }).then(() => console.log('async2'));
 };
async function fn2() {
    await 2
    console.log(2)
}
//等价于
async function fn2() {
    Promise.resolve(2).then(() => console.log(2))
}
4.3、await的返回值
async function test() {
    let result = await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('返回的值');
        }, 1000);
    });
   console.log('111',result) 
   // 上面的等价于Promise.resolve(result).then(() => console.log(‘111’,result));
   return result;
};
test().then((data) =>
    console.log(data)
)
// (1s后) 111 返回的值
// (1s后) 返回的值

我们可以发现,await 的返回值是promise的resolve/reject 返回结果,然后通过执行async函数 retrun出来,相当于 Promise.resolve("返回结果")!

5、两道面试题
5.1、宏微任务执行顺序问题

既然说到了这里,看下面的一道宏任务和微任务的面试题!

async function test() {
    console.log("async1");
    await new Promise((resolve, reject) => {
        resolve(1)
    });
    console.log('async2');
};
setTimeout(function () {
    console.log("setTimeout");
}, 0);
test();
new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("promise2");
});
console.log('end');

相信很多人都会做错,那么我们来分析下!

  1. 执行第一轮宏任务,发现setTimeout是个宏任务,0秒后直接扔到宏任务异步队列!
  2. 执行test(),先输出"async1",继续执行await,因为后面是个promise对象,所以会"阻塞"后面的任务,直到拿到1这个结果。这里是重点:发现await后面有个console.log(‘async2’),则根据刚才的知识,我们知道执行完await后面的promise,那么后面的代码都是在promise.then()里面执行,所以console.log('async2')是个微任务!,把它扔进异步队列!
  3. 执行new Promise,执行同步代码输出"promise1",then()后面又是个微任务,扔进任务异步队列!
  4. 执行同步代码输出’end’!
  5. 一轮宏任务执行完毕,看下有没有微任务,执行第一个微任务输出’async2’,再执行第二个微任务"promise2",微任务执行结束,再执行第二轮宏任务!
  6. 最后执行宏任务输出"setTimeout"

所以执行结果是

async1
promise1
end
async2
promise2
setTimeout
5.2、输出值问题
let a = 0
let b = async () => {
  a = a + await 10
  console.log('2', a) 
}
b()
a++
console.log('1', a) 

先说结果,正确答案是

1 1   //先打印
2 10  //后打印

让我来解释下原因

  • 首先函数 b 先执行,在执行到 await 10 之前变量a 还是 0,因为await 内部实现了 generatorgenerator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
  • 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
  • 同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候a = 0 + 10

所以上述代码可以转换成

var a = 0
var b = async () => {
   Promise.resolve(10).then(res=>{
       a = 0 + res //这里的0就是值得是await缓存的a的值0
   }).then(()=>{
        console.log('2', a) 
   })
}
b()
a++
console.log('1', a)

上述解释中提到了await 内部实现了 generator,其实 await就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator

6、async抛出异常

async函数相当于对多个Promise的封装,所以必须等到内部所有的await命令执行完,才会改变自己的状态为resolved除非碰到return语句或者抛出了异常
也就是说,正常情况下 只有async函数内部的异步操作执行完,才会执行then后面的语句。

当async函数抛出异常时或者await的返回值reject状态,Promise 的 reject 方法也会传递这个异常值!

async function test() {
    throw Error("获取错误")
};
test().catch((error) =>
    console.log(error)
);
//Error: 获取错误

只要一个await后面的Promise变为rejected,整个async函数就会中断执行,整个async返回的Promise对象就会是rejected状态。

async function test() {
    let result = await new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("获取返回值失败");
        }, 1000);
    });
    //reject后,后面console.log('111',result)代码不执行了
    console.log('111',result) 
    return result;
};
test().catch((error) =>
    console.log('222',error)//获取‘返回值失败’
);
// 222 获取返回值失败
async function f() {
  	await Promise.reject('出错了');
  	await Promise.resolve('hello world'); // 这行代码不会执行
}

因为第一个await后面的对象reject了,所以整个async函数就中断执行了

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。
这时可以将第一个await放在try…catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。

7、try catch

try catch是JavaScript的异常处理机制,把可能出错的代码放在try语句块中,如果出错了,就会被catch捕获来处理异常。如果不catch 一旦出错就会造成程序崩溃。

如果有多个await命令,可以将其都放在try catch结构中,如果执行出错,catch会去捕获异常

async function f() {
    try {
     	await Promise.reject('出错了');
     	console.log('上面已经出错了'); //这行代码不会执行
     	return await Promise.resolve('hello world'); //这行代码不会执行
    } catch(e) {
        console.log('111',e);
    }
 }
  
f()
.then(v => console.log('222',v))
.catch(k => console.log('333' , k))
// 111 出错了
// 222 undefined

catch会去捕获 try 代码块中的错误,只要有一个抛出了异常,就不会继续执行
所以上面的代码不会打印“上面已经出错了” ,也不会执行“return await Promise.resolve(‘hello world’)”;
因为使用了try catch 所以 async 是顺利执行完成的,其中的报错 被 try catch处理了,所以异常不会被async返回的Promise的catch捕获,因此async返回的Promise对象状态是resolved。

8、async await 和promise相比的优势

async await是promise的进化版,async await 相比原来的Promise的优势在于处理 then链,不必把回调嵌套在then中,只要await 即可
我们来模拟下一个场景,有3个请求,但是有顺序的,必须要先执行第一个,再执行第二个,最后执行第三个,并且1秒后执行第一个请求,执行完第一个请求后,3秒后执行第二个请求,执行完第二个请求后,5秒后执行第三个请求!

promise会这样写

let request1 = (time) => {
     return new Promise((resolve, reject) => {
         setTimeout(() => {
             resolve('request1返回的值');
         }, time * 1000);
     });
 };

 let request2 = (time) => {
     return new Promise((resolve, reject) => {
         setTimeout(() => {
             resolve('request2返回的值');
         }, time * 1000);
     });
 };

 let request3 = (time) => {
     return new Promise((resolve, reject) => {
         setTimeout(() => {
             resolve('request3返回的值');
         }, time * 1000);
     });
 };

 request1(1).then((data) => {
     console.log(data);//request1返回的值
     return request2(3)
 }).then((data) => {
     console.log(data);//request3返回的值
     return request3(5)
 }).then((data) => {
     console.log(data)//request3返回的值
 })

async await 会这样写

async function request() {
    let resolve1 = await request1(1);
    console.log(resolve1);
    resolve2 = await request2(3);
    console.log(resolve2);
    resolve3 = await request3(5);
    console.log(resolve3);
}
request();
9、须知

对于Promise状态的相关说明,官网上其实已经表达的很清楚了,下面这段话摘自MDN对Promise的描述:

  • 注意: 如果一个 promise 已经被兑现(fulfilled)被拒绝(rejected),那么我们也可以说它处于已敲定(settled)状态。您还会听到一个经常跟 promise 一起使用的术语:已决议(resolved),它表示 promise 已经处于已敲定(settled)状态,或者为了匹配另一个 promise 的状态被"锁定"了。

(附带配图)
在这里插入图片描述
即:

状态概念解析:resolved状态 = Settled状态 = fulfilled状态 + rejected状态
—————————————————————————————————

(本文部分参考博主「世态炎凉!!」的文章,原文链接:https://blog.csdn.net/ws9029/article/details/111402211

  • 7
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值