当前位置: 首页 > news >正文

深入理解JavaScript异步编程:从回调地狱到Promise/Async优雅解决方案

一、同步代码和异步代码

1.同步代码的定义

逐行执行,原地等待结果后才继续向下执行
浏览器按照代码书写顺序逐行解析和执行,上一行代码执行完成后才会执行下一行,后续代码依赖前一行的执行结果。这种执行方式具有顺序性和阻塞性,即必须等待当前代码执行完毕才能继续后续操作。

  • 特点:顺序执行、阻塞主线程、后续代码依赖前一行结果。
  • 示例:普通赋值语句(const result = 0 + 1)、函数调用等。

2.异步代码的定义

调用后耗时,不阻塞代码继续执行,在将来完成后触发一个回调函数
异步代码在调用后不会阻塞主线程,而是将耗时任务交给宿主环境(如浏览器)处理,主线程继续执行后续代码。当异步任务完成时,通过回调函数处理结果。

  • 特点:非阻塞性、通过回调函数处理结果、不影响主线程后续代码执行。
  • 常见场景setTimeout/setInterval、事件监听(如点击事件)、AJAX 请求等。

3.核心区别

特性同步代码异步代码
执行方式逐行顺序执行,阻塞主线程非阻塞,异步任务后台处理
结果处理直接获取结果通过回调函数处理结果
典型场景普通赋值、函数调用定时器、事件、AJAX 请求

二、回调函数地狱和Promise 链式调用

1.回调函数地狱

需求:展示默认第一个省,第一个城市,第一个地区在下拉菜单中

概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱

缺点:可读性差,异常无法捕获,耦合性严重,牵一发动全身

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>回调地狱</title>
</head><body><form><span>省份:</span><select><option class="province"></option></select><span>城市:</span><select><option class="city"></option></select><span>地区:</span><select><option class="area"></option></select></form><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script><script>/*** 目标:演示回调函数地狱* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中* 概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱* 缺点:可读性差,异常无法获取,耦合性严重,牵一发动全身*/// 1. 获取默认第一个省份的名字axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {const pname = result.data.list[0]document.querySelector('.province').innerHTML = pname// 2. 获取默认第一个城市的名字axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }}).then(result => {const cname = result.data.list[0]document.querySelector('.city').innerHTML = cname// 3. 获取默认第一个地区的名字axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }}).then(result => {console.log(result)const areaName = result.data.list[0]document.querySelector('.area').innerHTML = areaName})})}).catch(error => {console.dir(error)})</script>
</body></html>

2.Promise - 链式调用

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Promise_链式调用</title>
</head><body><script>/*** 目标:掌握Promise的链式调用* 需求:把省市的嵌套结构,改成链式调用的线性结构*/// 1. 创建Promise对象-模拟请求省份名字const p = new Promise((resolve, reject) => {setTimeout(() => {resolve('北京市')}, 2000)})// 2. 获取省份名字const p2 = p.then(result => {console.log(result)// 3. 创建Promise对象-模拟请求城市名字// return Promise对象最终状态和结果,影响到新的Promise对象return new Promise((resolve, reject) => {setTimeout(() => {resolve(result + '--- 北京')}, 2000)})})// 4. 获取城市名字p2.then(result => {console.log(result)})// then()原地的结果是一个新的Promise对象console.log(p2 === p)</script>
</body></html>


<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Promise链式调用_解决回调地狱</title>
</head><body><form><span>省份:</span><select><option class="province"></option></select><span>城市:</span><select><option class="city"></option></select><span>地区:</span><select><option class="area"></option></select></form><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script><script>/*** 目标:把回调函数嵌套代码,改成Promise链式调用结构* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中*/let pname = ''// 1. 得到-获取省份Promise对象axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {pname = result.data.list[0]document.querySelector('.province').innerHTML = pname// 2. 得到-获取城市Promise对象return axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})}).then(result => {const cname = result.data.list[0]document.querySelector('.city').innerHTML = cname// 3. 得到-获取地区Promise对象return axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})}).then(result => {console.log(result)const areaName = result.data.list[0]document.querySelector('.area').innerHTML = areaName})</script>
</body></html>

三、async 和await 使用

1.async 函数的定义

使用 async 关键字声明的函数,返回一个 Promise,并允许在其内部使用 await 关键字

  • async 函数会隐式地将返回值包装为 Promise,即使没有显式返回 Promise,也会返回 Promise.resolve(返回值)
  • 作用:简化异步代码的书写,使异步逻辑更接近同步代码的写法,避免回调地狱。
  • 语法:
    async function 函数名([参数]) {// 异步操作或同步逻辑return 结果; // 隐式包装为 Promise
    }
    

2.await 关键字的定义

用于等待一个 Promise 解决(resolve),并获取其结果值,只能在 async 函数内部使用

  • 当 await 后面的 Promise 未解决时,会暂停 async 函数的执行,直到 Promise 状态变为 fulfilled,并返回其结果值。
  • 若 await 后面是普通值(非 Promise),会直接返回该值(等效于 Promise.resolve(普通值))。
  • 作用:以同步代码的写法处理异步逻辑,使异步操作更直观。
  • 语法:
    const 结果 = await Promise或普通值;

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>async函数和await_解决回调函数地狱</title>
</head><body><form><span>省份:</span><select><option class="province"></option></select><span>城市:</span><select><option class="city"></option></select><span>地区:</span><select><option class="area"></option></select></form><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script><script>/*** 目标:掌握async和await语法,解决回调函数地狱* 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值* 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)*/// 1. 定义async修饰函数async function getData() {// 2. await等待Promise对象成功的结果const pObj = await axios({url: 'http://hmajax.itheima.net/api/province'})const pname = pObj.data.list[0]const cObj = await axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})const cname = cObj.data.list[0]const aObj = await axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})const areaName = aObj.data.list[0]document.querySelector('.province').innerHTML = pnamedocument.querySelector('.city').innerHTML = cnamedocument.querySelector('.area').innerHTML = areaName}getData()</script>
</body></html>

3.async函数和await_捕获错误

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>async函数和await_错误捕获</title>
</head><body><form><span>省份:</span><select><option class="province"></option></select><span>城市:</span><select><option class="city"></option></select><span>地区:</span><select><option class="area"></option></select></form><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script><script>/*** 目标:async和await_错误捕获*/async function getData() {// 1. try包裹可能产生错误的代码try {const pObj = await axios({ url: 'http://hmajax.itheima.net/api/province' })const pname = pObj.data.list[0]const cObj = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })const cname = cObj.data.list[0]const aObj = await axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })const areaName = aObj.data.list[0]document.querySelector('.province').innerHTML = pnamedocument.querySelector('.city').innerHTML = cnamedocument.querySelector('.area').innerHTML = areaName} catch (error) {// 2. 接着调用catch块,接收错误信息// 如果try里某行代码报错后,try中剩余的代码不会执行了console.dir(error)}}getData()</script>
</body></html>

四、事件循环-EventLoop

1.认识- 事件循环(EventLoop)

2.事件循环- 执行过程


<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>事件循环_练习</title>
</head><body><script>/*** 目标:阅读并回答执行的顺序结果*/console.log(1)setTimeout(() => {console.log(2)}, 0)function myFn() {console.log(3)}function ajaxFn() {const xhr = new XMLHttpRequest()xhr.open('GET', 'http://hmajax.itheima.net/api/province')xhr.addEventListener('loadend', () => {console.log(4)})xhr.send()}for (let i = 0; i < 1; i++) {console.log(5)}ajaxFn()document.addEventListener('click', () => {console.log(6)})myFn()// 1 5 3 2 4 点击一次document就会执行一次打印6</script>
</body></html>

3.宏任务与微任务


4.宏任务与微任务- 执行顺序

5.事件循环- 经典面试题

console.log(1); // 1
setTimeout(() => { // 宏任务1(延迟0ms)console.log(2);const p = new Promise(resolve => resolve(3));p.then(result => console.log(result)); // 微任务3
}, 0);const p = new Promise(resolve => { // 同步执行Promise构造函数setTimeout(() => { // 宏任务2(延迟0ms)console.log(4);}, 0);resolve(5); // 触发微任务1
});
p.then(result => console.log(result)); // 微任务2const p2 = new Promise(resolve => resolve(6)); // 同步resolve
p2.then(result => console.log(result)); // 微任务4console.log(7); // 7

⑴.同步代码执行阶段

①console.log(1) 立即执行,输出 1。

②setTimeout(() => { console.log(2)... }, 0) 回调函数被放入宏任务队列(由于是 setTimeout)。

③const p = new Promise(...)

  • 构造函数中的 setTimeout(() => { console.log(4) }, 0) 被放入宏任务队列。
  • resolve(5) 立即执行,Promise p 变为 resolved 状态。
  • p.then(...) 将回调 console.log(5) 放入微任务队列。

④ const p2 = new Promise(resolve => resolve(6)) resolve(6) 立即执行,Promise p2 变为 resolved 状态。 p2.then(...) 将回调 console.log(6) 放入微任务队列。

⑤console.log(7) 立即执行,输出 7。 此时同步代码执行完毕,输出顺序为:1 → 7。

⑵.微任务队列处理

执行 p.then(() => console.log(5))
输出 5

执行 p2.then(() => console.log(6))
输出 6

微任务队列清空,输出顺序更新为:1 → 7 → 5 → 6


⑶.宏任务队列处理

①执行第一个 setTimeout 的回调(对应 console.log(2)):

  • 输出 2
  • new Promise(resolve => resolve(3)) 立即执行,Promise 状态变为 resolved
  • p.then(() => console.log(3)) 将回调 console.log(3) 放入微任务队列

②当前宏任务结束,立即处理微任务队列

  • 执行 console.log(3),输出 3
  • 执行第二个 setTimeout 的回调(对应 console.log(4)):
  • 输出 4

最终输出顺序为:1 → 7 → 5 → 6 → 2 → 3 → 4

五、Promise.all 静态方法


<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Promise的all方法</title>
</head><body><ul class="my-ul"></ul><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script><script>/*** 目标:掌握Promise的all方法作用,和使用场景* 业务:当我需要同一时间显示多个请求的结果时,就要把多请求合并* 例如:默认显示"北京", "上海", "广州", "深圳"的天气在首页查看* code:* 北京-110100* 上海-310100* 广州-440100* 深圳-440300*/// 1. 请求城市天气,得到Promise对象const bjPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '110100' } })const shPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '310100' } })const gzPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440100' } })const szPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440300' } })// 2. 使用Promise.all,合并多个Promise对象const p = Promise.all([bjPromise, shPromise, gzPromise, szPromise])p.then(result => {// 注意:结果数组顺序和合并时顺序是一致console.log(result)const htmlStr = result.map(item => {return `<li>${item.data.data.area} --- ${item.data.data.weather}</li>`}).join('')document.querySelector('.my-ul').innerHTML = htmlStr}).catch(error => {console.dir(error)})</script>
</body></html>

六、案例- 商品分类

七、案例- 学习反馈

代码详见day04

http://www.xdnf.cn/news/163819.html

相关文章:

  • Eigen核心矩阵/向量类 (Matrix, Vector, Array)
  • 循环神经网络RNN---LSTM
  • 函数递归之青蛙跳台阶+汉诺塔
  • 网络原理 - 8
  • 某海关某署 【瑞数6】逆向分析
  • 矩阵系统私信功能开发技术实践,支持OEM
  • Eigen的主要类及其功能
  • ACPs:面向智能体互联网的智能体协作协议体系
  • 经典反转结构——案例分析
  • 《算法竞赛进阶指南》0x20章目录
  • 57常用控件_QLineEdit的属性
  • 使用css修饰网页元素
  • 聚合分销系统开发:短剧小说外卖网盘电商cpscpa系统
  • PCL点云处理之基于FPFH特征的SAC-IA全局配准算法 (二百四十六)
  • 基于javaweb的SpringBoot小说阅读系统设计与实现(源码+文档+部署讲解)
  • Unity网络编程入门:掌握Netcode for GameObjects实现多人游戏基础(Day 39)
  • dubbo 隐式传递
  • MATLAB 2022a 部分讲解
  • 类和对象(下)
  • 综述类论文读后报告——重庆大学《深度学习在人类活动识别中的应用综述》
  • 16. LangChain自主智能体(Autonomous Agent):模拟人类工作流的进阶设计
  • 4.26-count部分的渲染
  • 参考平面的宽度-信号与电源完整性分析
  • 云原生--核心组件-容器篇-3-Docker核心之-镜像
  • 考研系列-计算机组成原理第四章、指令系统
  • 012组合数学——算法备赛
  • [创业之路-390]:人力资源 - 社会性生命系统的解构与重构:人的角色嬗变与组织进化论
  • 前端职业发展:如何规划前端工程师的成长路径?
  • RAG技术解析:以Text2SQL为例看检索增强生成的全流程应用
  • 第1章 基础知识