异步async和await
async和await使基于Promise的异步操作更简洁方便,Promise是解决回调地狱问题,async和await则是使异步的代码看起来像同步的代码,更容易理解
async和await是什么
可以声明一个async函数,表示函数里面有异步操作,一般只有在async函数中才能使用await;
await异步等待,表示紧跟在await后面的是异步的,需要等待结果,await需要和async一起使用
基本语法:
async function fn() { const result = await 异步操作(Promise对象) }为什么用?使异步操作更简洁方便;使异步代码看起来像同步,更容易理解
使用async 和 await
实现一个效果,一个广告图片一开始是消失的,然后隔几秒显示,然后又消失
回调函数方式:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .ad{ display: block; width: 300px; height: 150px; margin: 0 auto; } .none{ display: none; } </style></head><body> <img src="./ad.jpg" alt="" class="ad none" id="ad"> <script> // 回调函数 function ad () { // 获取DOM const $ad = document.getElementById('ad'); setTimeout(() => { // 1S后移除 $ad.classList.remove('none') setTimeout(() => { // 2S后隐藏 $ad.classList.add('none') }, 2000) }, 1000) } ad() </script></body></html>Promise方式
function wait (ms) { return new Promise((resolve) => { setTimeout(resolve, ms) })}function ad () { const $ad = document.getElementById('ad'); wait(1000).then(() => { // 1S后显示ad $ad.classList.remove('none') return wait(2000) }).then(() => { // 2S后隐藏ad $ad.classList.add('none') return wait(3000) }).then(() => { // 3S后重新显示ad $ad.classList.remove('none') })}ad()async方式
function wait (ms) { return new Promise((resolve) => { setTimeout(resolve, ms) })}async function ad () { const $ad = document.getElementById('ad'); await wait(1000) // 1S后显示ad $ad.classList.remove('none') await wait(2000) // 2S后隐藏ad $ad.classList.add('none') await wait(3000) // 3S后重新显示ad $ad.classList.remove('none')}ad()async函数的返回值
async函数的返回值是Promise对象,观察:
async function fn () { return 'hello world' //相当于 return Promise.resolve('hello world')}console.log(fn());async函数的各种形式
函数声明:
async function fn () {}函数表达式
const fn = async function(){}箭头函数
const fn = async()=>{}箭头函数单参数
const fn = async param => {}对象方法
const obj = { fn:async function(){}, fn1:async ()=>{}, async fn(){}}class的方法
class ClassName{ async fn(){}}async函数的注意事项
async函数返回值是一个成功状态的Promise,如果需要失败的,需要手动设置
第一种方式:使用Promise.reject
async function fn () { // return 123 return Promise.reject(new Error('出错了')) }
fn().catch(err => { console.log(err); }) console.log(fn());第二种方式,使用throw new Error,当然,这没必要可以去报错,如果逻辑错误了,Promise就是失败态
async function fn () { // return 123 // return Promise.reject(new Error('出错了')) try { throw new Error('出错了') } catch (error) { console.log(error); }}
console.log(fn());async函数可以没有await,反过来则不行
await的机制
沿用之前广告显示隐藏的例子
function wait (ms) { return new Promise((resolve) => { setTimeout(resolve, ms) })}async function ad () { const $ad = document.getElementById('ad'); await wait(1000) // 1S后显示ad $ad.classList.remove('none') await wait(2000) // 2S后隐藏ad $ad.classList.add('none') await wait(3000) // 3S后重新显示ad $ad.classList.remove('none')}ad()这里的await wait(1000)相当于阻塞了1秒后才执行后面的代码,相当于以下代码
wait(1000).then(() => { // 1S后显示ad $ad.classList.remove('none') return wait(2000)}).then(() => { // 2S后隐藏ad $ad.classList.add('none') return wait(3000)}).then(() => { // 3S后重新显示ad $ad.classList.remove('none')})结论:async函数中的代码有先后关系,await会阻塞该async函数中代码的执行,注意:await 确实会暂停 async 函数后续代码的执行,但它不会阻塞整个 JavaScript 主线程
await的值
await 表达式的返回值,取决于它后面跟的是什么。核心规则:await 会等待 Promise 完成,并取出它成功完成时的结果;如果后面不是 Promise,则直接得到该值。
await 后面的值 | await 表达式的结果 |
|---|---|
Promise.resolve(100) | 100 |
Promise.reject('失败') | 抛出 '失败' |
100 | 100 |
'hello' | 'hello' |
{ age: 18 } | { age: 18 } |
fetch('/api/data') | 请求成功后得到 Response 对象 |
await的注意事项
- await 只能在 async 函数中使用;注意:ES 模块还支持在函数外使用顶层
await。
async function fn() { const result = await Promise.resolve(100); console.log(result);}
fn();- await 只暂停当前函数的后续执行;
await不会阻塞整个 JavaScript 主线程,其他代码仍然可以执行。
async function fn() { console.log(1); await Promise.resolve(); console.log(2);}
fn();console.log(3);- await 后面可以跟普通值,不一定是 Promise;如果后面不是 Promise 或其他 thenable 对象,
await会将其作为已经完成的值处理。
async function fn() { console.log(await 100); // 100 console.log(await 'hello'); // hello}
fn();- await 遇到 rejected Promise 会抛出错误;因此,等待可能失败的异步操作时,可以使用
try...catch处理错误。
async function fn() { try { const result = await Promise.reject('请求失败'); } catch (error) { console.log(error); // 请求失败 }}
fn();- 多个 await 默认按顺序等待
async function fn() { const a = await task1(); const b = await task2();}如果 task2() 必须等待 task1() 完成后才能开始,这样写是合适的。
但如果两个任务相互独立,这样写会让它们依次执行,可以改为:
async function fn() { const [a, b] = await Promise.all([ task1(), task2() ]);}这样两个任务可以同时启动,不必等待第一个任务完成后才启动第二个。
- 不要误以为 await 会自动等待所有异步任务:例如,
forEach()不会等待异步回调全部执行完毕:
async function fn() { [1, 2, 3].forEach(async item => { await task(item); });
console.log('执行完毕');}这里的 '执行完毕' 不会等待所有 task() 完成。
如果需要依次执行,使用 for...of:
async function fn() { for (const item of [1, 2, 3]) { await task(item); }
console.log('全部执行完毕');}如果需要并发执行并等待全部完成,可以使用 Promise.all()。
速记表
| 注意事项 | 核心要点 |
|---|---|
| 使用位置 | async 函数中,或 ES 模块顶层 |
| 执行机制 | 暂停当前异步函数的后续执行,不阻塞主线程 |
| 等待对象 | 可以是 Promise,也可以是普通值 |
| 错误处理 | Promise 被拒绝时,await 会抛出错误 |
| 多个任务 | 有依赖就顺序等待,互相独立可考虑 Promise.all() |
| 循环处理 | forEach() 不会等待异步回调完成 |
其中,最值得掌握的是顺序等待与并发等待的区别,以及 await 的错误处理。这两点在实际开发中尤其常用。
继发和并发
继发(顺序执行)前一个任务完成后,才开始执行下一个任务。
async function fn() { const a = await task1(); const b = await task2();
console.log(a, b);}并发(同时启动):多个任务不必等待彼此完成,可以同时启动,然后统一等待结果。
async function fn() { const p1 = task1(); const p2 = task2(); const [a, b] = await Promise.all([p1, p2]); console.log(a, b);}项目应用实战
二级菜单
初始化代码
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <title>二级菜单</title> <style> /* css reset */ * { padding: 0; margin: 0; } li { list-style: none; }
/* menu */ .menu { width: 100px; background-color: rgba(0, 0, 0, 0.1); margin: 10px; } .menu-item { position: relative; padding: 5px; cursor: pointer; } .menu-content { display: none; position: absolute; left: 100%; top: 0; width: 200px; height: 100px; padding: 0 5px; background-color: rgba(0, 0, 0, 0.1); } .menu-item:hover { background-color: rgba(0, 0, 0, 0.4); } .menu-item:hover .menu-content { display: block; } .menu-loading { margin: 45px 0 0 92px; } </style> </head> <body> <ul id="menu" class="menu"> <!-- <li class="menu-item" data-key="hot" data-done="done"> <span>热门出发地</span> <div class="menu-content"> <img class="menu-loading" src="./loading.gif" alt="加载中" /> <p>内地热门城市</p> <p>热门景点</p> </div> </li> --> </ul>
<script> // https://www.imooc.com/api/mall-PC/index/menu // https://www.imooc.com/api/mall-PC/index/menu/hot // https://www.imooc.com/api/mall-PC/index/menu/hk function wait(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); } function getJSON(url) { return new Promise(resolve => { const xhr = new XMLHttpRequest();
xhr.addEventListener( 'load', async () => { if ( (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 ) { // 模拟网络延迟 await wait(1000); resolve(xhr.response); } }, false );
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.send(null); }); } </script> </body></html>实现:
(async function () { const url = 'https://www.imooc.com/api/mall-PC/index/menu' // 发送请求 const menuResult = await getJSON(url); const $menu = document.getElementById('menu') console.log(menuResult); createMenu($menu, menuResult.data); // 循环绑定事件,发送请求,获取数据,创建子菜单 const $item =$menu.getElementsByClassName('menu-item'); [...$item].forEach($item => { $item.addEventListener('mouseenter', async () => { if ($item.dataset.done === 'done') return; const subMenuResul = await getJSON(`https://www.imooc.com/api/mall-PC/index/menu/${$item.dataset.key}`) $item.dataset.done = 'done' console.log(subMenuResul); createsubMenu($item, subMenuResul.data) },false) });})()function createMenu ($el, data) { let html = '' for (const item of data) { html += ` <li class="menu-item" data-key="${item.key}"> <span>${item.title}</span> <div class="menu-content"> <img class="menu-loading" src="./loading.gif" alt="加载中" /> ` $el.innerHTML = html }}function createsubMenu ($el, data) { let html = '' for (const item of data) { html += ` <p>${item.title}</p> ` } $el.querySelector('.menu-content').innerHTML = html}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!














