三、async/await
现代 js
的异步开发,基本上被 async
和 await
给承包和普及了。虽然说 promise
中的 .then
和 .catch
已经很简洁了,但是 async
更简洁,它可以通过写同步代码来执行异步的效果。如此神奇的 async
和 await
究竟是什么呢?让我们一起来一探究竟吧!
1、引例阐述
先用一个例子来展示 promise
和 async/await
的区别。假设我们现在要用异步来实现加载图片。
(1) 如果用 promise
的 .then
和 .catch
实现时,代码如下:
function loadImg(src){ const picture = new Promise( (resolve, reject) => { const img = document.createElement('img'); img.onload = () => { resolve(img); } img.onerror = () => { const err = new Error(`图片加载失败 ${src}`); reject(err); } img.src = src; } ) return picture; } const url1 = 'https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2144980717,2336175712&fm=58'; const url2 = 'https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=614367910,2483275034&fm=58'; loadImg(url1).then(img1 => { console.log(img1.width); return img1; //普通对象 }).then(img1 => { console.log(img1.height); return loadImg(url2); //promise 实例 }).then(img2 => { console.log(img2.width); return img2; }).then(img2 => { console.log(img2.height); }).catch(ex => console.error(ex)); 复制代码
(2) 如果用 async
实现时,代码如下:
function loadImg(src){ const picture = new Promise( (resolve, reject) => { const img = document.createElement('img'); img.onload = () => { resolve(img); } img.onerror = () => { const err = new Error(`图片加载失败 ${src}`); reject(err); } img.src = src; } ) return picture; } const url1 = 'https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2144980717,2336175712&fm=58'; const url2 = 'https://dss2.bdstatic.com/8_V1bjqh_Q23odCf/pacific/1990552278.jpg'; !(async function () { // img1 const img1 = await loadImg(url1); console.log(img1.width, img1.height); // img2 const img2 = await loadImg(url2); console.log(img2.width, img2.height); })();
大家可以看到,如果用第二种方式的话,代码要优雅许多。且最关键的是,通过 async
和 await
,用同步代码就可以实现异步的功能。接下来我们开始来了解 async
和 await
。
2、async和await
- 背景:解决异步回调问题,防止深陷回调地狱
Callback hell
; Promise
:Promise then catch
是链式调用,但也是基于回调函数;async/await
:async/await
是同步语法,彻底消灭回调函数,是消灭异步回调的终极武器。
3、async/await和promise的关系
(1) async/await
与 promise
并不互斥,两者相辅相成。
(2) 执行 async
函数,返回的是 promise
对象。
(3) await
相当于 promise
的 then
。
(4) try…catch
可以捕获异常,代替了 promise
的 catch
。
接下来我们来一一(2)(3)(4)演示这几条规则。
第一条规则: 执行 async
函数,返回的是 promise
对象。
async function fn1(){ return 100; //相当于return promise.resolve(100); } const res1 = fn1(); //执行async函数,返回的是一个Promise对象 console.log('res1', res1); //promise对象 res1.then(data => { console.log('data', data); //100 }); 复制代码
在以上的这段代码中,控制台打印结果如下。
大家可以看到,第一个 res1
返回的是一个 promise
对象,且此时 promise
对象的状态是 fulfilled
状态,所以可以调用后续的 .then
并且打印出 data 100
。
第二条规则: await
相当于 promise
的 then
。
!(async function (){ const p1 = Promise.resolve(300); const data = await p1; //await 相当于 promise的then console.log('data', data); //data 300 })(); !(async function () { const data1 = await 400; //await Promise.resolve(400) console.log('data1', data1); //data1 400 })();
在以上的这段代码中,控制台打印结果如下。
大家可以看到, p1
调用 resolve
回调函数,所以此时 p1
属于 fulfilled
状态,之后 const data = await p1
中的await
,相当于 promise
的 then
,又因为此时 p1
属于 fulfilled
状态,所以可以对 .then
进行调用,于是输出 data 300
。同理在第二段代码中, await 400
时, 400
即表示 Promise.resolved(400)
,因此属于 fulfilled
状态,随后调用 .then
,打印出 data1 400
结果。
再来看一段代码:
!(async function (){ const p2 = Promise.reject('err1'); const res = await p4; //await 相当于 promise的then console.log('res', res); //不打印 })(); 复制代码
在以上的这段代码中,控制台打印结果如下。
大家可以看到, p2
调用 reject
回调函数,所以此时 p2
属于 reject
状态。但因为await是触发 promise
中的 .then
,所以此时 res
不会被触发,于是后续不会对await进行操作,控制台也就不对 console.log('res', res);
进行打印。
第三条规则: try…catch
可以捕获异常,代替了 promise
的 catch
。
!(async function () { const p3 = Promise.reject('err1'); //rejected 状态 try{ const res = await p3; console.log(res); }catch(ex){ console.error(ex); //try…catch 相当于 promise的catch } })();
在以上的这段代码中,控制台打印结果如下。
大家可以看到, p3
调用 reject
回调函数,所以此时 p3
属于 rejected
状态,因此它不会执行 try
的内容,而是去执行 catch
的内容, try…catch
中的 catch
就相当于 promise
中的 catch
,且此时 p3
属于 rejected
状态,因此执行 catch
,浏览器捕获到异常,报出错误。
4、异步的本质
从上面的分析中,不管是 promise
还是 async/await
,都是解决异步问题。但是呢,异步的本质还是解决同步的问题,所以,异步的本质是:
async/await
是消灭异步回调的终极武器;JS
是单线程的,需要有异步,需要基于event loop
;async/await
是一个语法糖,但是这颗糖非常好用!!
我们来看两道 async/await
的顺序问题,回顾 async/await
。
第一题:
async function async1(){ console.log('async start'); // 2 await async2(); //await 的后面,都可以看做是callback里面的内容,即异步。 //类似event loop //Promise.resolve().then(() => console.log('async1 end')) console.log('async1 end'); // 5 } async function async2(){ console.log('async2'); //3 } console.log('script start'); // 1 async1(); console.log('script end'); // 4
在以上的这段代码中,控制台打印结果如下。
从上面这段代码中可以看到,先执行同步代码 1
,之后执行回调函数 async1()
,在回调函数 async1()
当中,先执行同步代码 2
,之后遇到 await
,值得注意的是, await
的后面,都可以看作是 callback
里面的内容,即异步内容,所以,先执行 await
中对应的 async2()
里面的内容,之后把 await
后面所有的内容放置到异步当中。继续执行 4
,等到 4
执行完时,整个同步代码已经执行完,最后,再去执行异步的代码,最终输出 5
的内容。
同样的方式来来分析第二题。
第二题:
async function async1(){ console.log('async1 start'); //2 await async2(); // 下面三行都是异步回调,callback的内容 console.log('async1 end'); //5 await async3(); // 下面一行是回调的内容,相当于异步回调里面再嵌套一个异步回调。 console.log('async1 end 2'); //7 } async function async2(){ console.log('async2'); //3 } async function async3(){ console.log('async3'); //6 } console.log('script start'); //1 async1(); console.log('script end'); //4
在以上的这段代码中,控制台打印结果如下。
这里就不再进行分析啦!大家可以根据第一个案例的步骤进行分析。
5、场景题
最后的最后,我们再来做两道题回顾我们刚刚讲过的 async/await
知识点。
(1)async/await语法
async function fn(){ return 100; } (async function(){ const a = fn(); //?? Promise const b = await fn(); //?? 100 })();
(async function(){ console.log('start'); const a = await 100; console.log('a', a); const b = await Promise.resolve(200); console.log('b', b); const c = await Promise.reject(300); //出错了,再往后都不会执行了 console.log('c', c); console.log('end'); })(); //执行完毕,打印出哪些内容? //start //100 //200
(2)async/await的顺序问题
async function async1(){ console.log('async1 start'); // 2 await async2(); //await后面的都作为回调内容 —— 微任务 console.log('async1 end'); // 6 } async function async2(){ console.log('async2'); // 3 } console.log('script start'); // 1 setTimeout(function(){ //宏任务 —— setTimeout console.log('setTimeout'); // 8 }, 0); //遇到函数,立马去执行函数 async1(); //初始化promise时,传入的函数会立刻被执行 new Promise(function(resolve){ //promise —— 微任务 console.log('promise1'); // 4 resolve(); }).then(function(){ //微任务 console.log('promise2'); // 7 }); console.log('script end'); // 5 //同步代码执行完毕(event loop —— call stack 被清空) //执行微任务 //(尝试触发 DOM 渲染) // 触发event loop,执行宏任务
这里就不再进行一一解析啦!大家可以前面知识点的学习总计再回顾理解。
四、写在最后
关于 js
的异步问题以及异步的解决方案问题就讲到这里啦!u1s1, promise
和 async/await
在我们日常的前端开发中还是蛮重要的,基本上写异步代码时候都会用到 async/await
来解决。啃了16个小时总结了event loop 和 promise 、async/await 问题,希望对大家有帮助。