前端异步编程基础
异步编程基础
本文主要介绍回调函数,Promise,aysnc/wait这三种异步机制,顺便说说axios,还有不要和我一样弄混ajax与异步的关系,正常走学校教学路线的人,可能第一个接触的就是ajax,刚好又不懂,所以可能和我一样弄混。除上述知识点,其他都是凑字数的。
注意AJAX不是前端异步的代名词
反正我学的时候老是容易搞混
混淆原因
早期 “AJAX” 一词被广泛用于指代 “异步网络请求”,因为在 AJAX 诞生初期(2005 年左右),它是前端实现异步通信的主要方式,因此很多人将 “异步” 与 “AJAX” 直接关联,AJAX 只是异步编程在 “浏览器与服务器通信” 场景下的应用
前端异步机制分类:通用机制与应用场景
一、异步编程通用机制(按实现方式分类)
机制类型 | 核心原理 | 典型实现 | 优缺点 |
---|---|---|---|
回调函数 | 通过函数参数传递异步结果,异步任务完成后调用回调函数 | setTimeout 、XMLHttpRequest 旧版 | - 简单直接 - 缺点:回调地狱、代码可读性差 |
Promise | 用状态机管理异步操作(pending/fulfilled/rejected),支持链式调用 | fetch() 、Promise.resolve() | - 避免回调嵌套 - 统一错误处理( .catch() )- 代码更清晰 |
async/await | Promise 的语法糖,以同步写法处理异步,自动返回 Promise | async function + await | - 最接近同步代码的写法 - 错误处理用 try...catch ,更符合直觉 |
Generator | 通过迭代器(Iterator)分段执行异步任务,需手动调用 next() 推进流程 | function* gen() { yield ... } | - 控制粒度更细 - 缺点:需手动管理流程,现代开发中较少使用 |
二、异步机制应用场景(按功能场景分类)
场景类型 | 核心功能 | 技术实现 | 典型案例 |
---|---|---|---|
网络通信 | 浏览器与服务器的异步数据交互,不阻塞页面 | fetch 、XMLHttpRequest 、WebSocket | - 动态加载数据(如商品列表) - 实时聊天WebSocket(异步双向通信) |
定时任务 | 按指定时间间隔执行异步操作 | setTimeout 、setInterval | - 轮询获取数据(如天气更新) - 动画帧控制( requestAnimationFrame ) |
用户交互 | 响应鼠标、键盘等用户操作,异步执行回调 | 事件监听(addEventListener ) | - 按钮点击、滚动加载更多 - 表单实时验证 |
并行计算 | 开启后台线程处理密集计算,不阻塞主线程(UI 渲染) | Web Worker 、SharedWorker | - 图片处理、大数据排序 - 游戏物理引擎计算 |
文件操作 | 异步读取/写入文件(主要在 Node.js 环境) | fs.readFile 、fs.writeFile | - 读取配置文件 - 写入日志数据 |
1. 异步编程有什么用?
有点后端基础的都知道,异步是在多线程学习时,才涉及到的概念,那么前端的这个异步有什么作用?
这里敲黑板!!!
前端的异步它并非解决多线程问题,它只是在表面上达到了多线程一样的效果,而是为了在单线程环境下实现非阻塞操作。
JavaScript主线程单线程的特性决定了同步操作会阻塞渲染,所以提出了异步的概念解决这个问题
JavaScript 是一种单线程语言,运行在浏览器的主线程中。这意味着在任何时刻,JavaScript 代码只能在一个线程上运行。无论是同步代码还是异步代码,它们都是在同一个线程上执行的。
总之,别把前端的异步和后端的异步扯上一点关系,当一个全新的东西去学就好了。
举个前端异步例子:
前端同步就像你洗澡,得先等热水器把水烧热,才能开始洗。要是热水器坏了,或者水烧得很慢,你就只能干等着,啥也干不了。
前端异步就好比你一边烧热水,一边看电视。热水烧着就烧着,你也不用一直盯着,可以先做别的事。等水烧好了,你再洗澡就行。这样,你的时间就被充分利用了,不会因为等待热水而浪费时间,整个过程也更高效。
2. 异步机制
异步机制 :允许在等待I/O(网络请求/文件读取)时释放主线程,保持页面渲染与处理流畅。它的核心目的是让程序在等待某个任务完成时,能够继续执行其他任务,而不是像同步代码那样一直阻塞等待。
所有异步机制最终都基于浏览器的 事件循环(Event Loop) 机制执行,这是 JS 异步编程的核心基础。
3. 前端有几种异步机制
前端中常见的异步机制包括回调函数
、Promise
、async/await
、和 Generator
。其中,Promise 和 async/await 是目前最常用和推荐的异步处理方式。
本文重点介绍前四种异步机制,最后一种说实话我还没真正用过。
3.1 回调函数(Callback)
回调函数是最传统的异步处理方式,通过将一个函数作为参数传递给另一个函数,并在异步操作完成时调用这个回调函数。
示例代码
//函数定义:fetchData 函数定义时,接受一个函数式的参数 callback。这个callback并没有具体定义,和普通参数一样,只是提供一个位置。
//这个参数是一个函数,意味着 fetchData 可以在执行过程中调用这个传入的函数。
function fetchData(callback) {//这里是故意的延时操作,模拟服务器2秒后加载回数据setTimeout(() => {console.log("数据加载完成!");callback("数据");}, 2000);
}fetchData((data) => {console.log("接收到数据:", data);
});
回调体现
函数定义:在 fetchData 函数内部,使用 setTimeout 模拟了一个异步操作(例如,从服务器加载数据)。当这个异步操作完成时(即 2 秒后),通过 callback(“数据”) 调用了传入的回调函数,并传递了 “数据” 作为参数。
回调函数的使用:在 fetchData 函数调用时,
传入了一个箭头函数 (data) => { console.log("接收到数据:", data); }
作为回调函数。
所以这个fetchData执行的步骤其实是这样的
1.进入setTimeOut函数内部
2.延时2秒
4. 输出"数据加载完成"日志
5.执行callback函数参数,而此时callback函数参数是(data) => { console.log(“接收到数据:”, data); }
6. 输出"接收到数据:数据"日志
顺带解释一下这里的箭头函数
箭头函数(基础知识点复习)
箭头函数是ES6引入的简洁函数语法,用=>
符号定义,通常省略function
关键字和return
,适用于单行表达式或需要绑定外层this
的场景。
例如传统函数:
function(a, b) { return a + b; }
可简化为箭头函数:
(a, b) => a + b
关键特性:
- 无独立
this
,继承父级作用域的this
- 不能用作构造函数(无
prototype
属性) - 适合回调函数或方法缩写
回调特点总结
- 简单直接,适用于简单的异步操作。
- 但容易出现“回调地狱”(Callback Hell),即嵌套过多的回调函数,代码难以维护。
3.2 Promise
Promise 是一种更现代的异步处理方式,它代表了一个异步操作的最终完成(或失败)及其结果值,其实写它的时候也会使用回调函数,只不过一个传统回调函数实现异步更像嵌套使用,后者是链式调用,代码可读性会好一丢丢,并且Promise有一定的定义和方法,方便处理异常情况。
注意这个目前Promise并没有特别合适的中文翻译,所以和别人讨论异步涉及它时,直接说这个单词就好了。
Promise状态
Promise 有三种状态:
pending
:初始状态,既不是成功也不是失败。
fulfilled
:操作成功完成。
rejected
:操作失败,(Promise任何一个环节出现错误都会切换到这个状态,当然也可以手动reject让Promise切换成rejected状态)
状态一旦改变(从 pending 变为 fulfilled 或 rejected),就会被固化,不会再变化。
Promise状态处理
Promise 提供 .then()、.catch() 和 .finally() 处理结果:
- .then() 可处理 fulfilled 或 rejected 状态(通过第二个回调参数,可以忽略处理rejected状态的处理,大多数人更习惯使用.catch来处理rejected状态)。
- .catch() 是专门处理 rejected 状态的语法糖(等价于 .then(null, onRejected))。
- .finally() 无论状态如何都会执行,用于清理操作。
示例代码:
function fetchData() {return new Promise((resolve, reject) => {setTimeout(() => {console.log("数据加载完成!");resolve("数据");// 如果出错可以用 reject("错误信息");}, 2000);});
}fetchData().then((data) => {console.log("接收到数据:", data);}).catch((error) => {console.error("发生错误:", error);});
Promise特点总结:
- 避免了回调地狱,代码更加清晰。
- 提供了错误处理机制(通过
.catch()
)。 - 可以通过
Promise.all()
等方法同时处理多个异步操作。
3.3 async/await
async 和 await 是 ES2017(ES8)引入的 JavaScript 语法,用于简化 Promise 的使用,让异步代码看起来更像同步代码。它们本质上是 Promise 的语法糖,但提供了更直观的写法。
用我自己的角度来理解,你想让异步操作,就在函数前面加个await,如果某个函数里加了await,那就必须使用aysnc保证它异步操作生效。并且await要使用try和catch进行异常捕获。
哪里需要等待异步结果,就在哪里加 await;加了 await 的函数必须用 async 修饰;为了安全,用 try…catch 捕获可能的错误。
aysnc
async 用于声明一个异步函数,它有两个特点:
- 自动返回 Promise:无论函数内部是否显式返回 Promise,async 函数的返回值都会被包装成 Promise。
- 支持 await:async 函数内部可以使用 await 关键字等待 Promise 完成。
await
await 只能在 async 函数内部使用,它的作用是:
- 暂停函数执行:等待 Promise 被解决(fulfilled 或 rejected)。
- 返回 Promise 的结果:如果 Promise 成功,则返回其结果值;如果失败,则抛出错误(需用 try…catch 捕获)。
示例代码:
// 模拟一个异步操作(如网络请求)
function simulateNetworkRequest() {console.log("开始请求数据...");// 返回一个 Promise,表示异步操作return new Promise((resolve) => {// 使用 setTimeout 模拟 2 秒的网络延迟setTimeout(() => {console.log("服务器已返回数据!");resolve("用户信息和文章列表"); // 数据加载完成,传递结果}, 2000);});
}// 使用 async 声明异步函数
async function fetchData() {console.log("进入 fetchData 函数");// await 会暂停当前函数的执行,等待 Promise 完成const result = await simulateNetworkRequest();// 这行代码会在 Promise 成功后才执行console.log("fetchData 函数继续执行");return result;
}// 主函数
async function main() {console.log("程序开始执行");try {console.log("等待数据加载...");// 调用异步函数并等待结果const data = await fetchData();// 这行代码会在 fetchData 完全执行完毕后才运行console.log("成功获取数据:", data);} catch (error) {console.error("获取数据失败:", error);}console.log("程序执行结束");
}// 启动主函数
main();
aysnc/wait特点总结:
- 代码更简洁,更接近同步代码的写法。
- 使用
try...catch
可以方便地捕获错误。 - 需要与 Promise 结合使用。
3.4 Generator(生成器)
Generator 是一种可以暂停和恢复执行的函数,虽然它本身不是直接用于异步操作,但可以通过手动控制暂停和恢复来实现异步效果。不过,Generator 的使用相对复杂,且不如 async/await
方便。
示例代码:
function* fetchData() {console.log("开始加载数据");yield new Promise((resolve) => {setTimeout(() => {console.log("数据加载完成!");resolve("数据");}, 2000);});
}const generator = fetchData();
const promise = generator.next().value;
promise.then((data) => {console.log("接收到数据:", data);
});
特点总结:
- 可以暂停和恢复函数执行。
- 需要手动管理暂停点和恢复逻辑。
- 使用场景较少,通常被
async/await
替代。
异步实现方法总结
前端中常见的异步机制包括回调函数、Promise、async/await
和 Generator已经基本介绍了一遍。说实话没有实践,这些理论都很难理解,就算理解了也真得很难形象地描述出来。
其中,Promise
和 async/await
是目前最常用和推荐的异步处理方式,因为它们提供了更简洁、更易读的代码风格和强大的错误处理机制。
4 axios:基于Promise的HTTP客户端
前文已经说过Promise和
async/await` 是目前最常用和推荐的异步处理方式,这么常用又好用的功能自然有人会想办法使用技术和它进行对接。
axios是一个流行的基于Promise的HTTP客户端,可以用于浏览器和Node.js环境。它提供了丰富的功能,如拦截请求和响应、取消请求、自动转换JSON数据等。
4.1 axios的基本用法
axios的核心API返回Promise,因此可以与async/await无缝集成:
// GET请求
axios.get('/api/users').then(response => console.log(response.data)).catch(error => console.error(error));
// POST请求
axios.post('/api/users', { name: 'John' }).then(response => console.log('创建成功:', response.data)).catch(error => console.error('创建失败:', error));
4.2 axios与async/await的结合
axios与async/await结合使用可以简化异步请求处理:
async function getUserData(userId) {try {const userResponse = await axios.get(`/api/users/${userId}`);const user = userResponse.data;const postsResponse = await axios.get(`/api/users/${userId}/posts`);const posts = postsResponse.data;return { user, posts };} catch (error) {console.error('获取数据失败:', error);throw error;}
}
4.3 axios的优势
- Promise基础:所有请求都返回Promise,可以与async/await完美结合
- 拦截器:可以拦截请求和响应,添加自定义逻辑(如认证、日志记录)
- 取消请求:支持请求取消,避免不必要的处理
- 自动转换JSON:自动将响应数据转换为JavaScript对象
- 浏览器兼容性:支持老版本浏览器,提供polyfill
5 技术对比与选择指南
5.1 Promise与async/await的区别
特性 | Promise | async/await |
---|---|---|
语法 | 链式调用 .then() | 类同步代码 |
错误处理 | .catch() | try/catch |
可读性 | 可能形成回调地狱 | 更接近自然语言 |
适用场景 | 简单异步操作 | 复杂异步流程 |
基础 | 是异步处理的基础 | 建立在Promise之上 |
5.2 axios与原生fetch的对比
特性 | axios | fetch |
---|---|---|
Promise基础 | 是 | 是 |
自动JSON转换 | 是 | 否 |
拦截器 | 支持 | 不支持 |
错误处理 | 自动处理网络错误 | 需要手动检查 |
浏览器兼容 | 广泛支持 | 现代浏览器 |
取消请求 | 支持 | 需要polyfill |
5.3 链式调用与同步异步的结合
在实现异步链式调用时,需要考虑:
- 同步链式调用:方法依次执行,每个方法返回this
- 异步链式调用:方法中包含异步操作,需要特殊处理执行顺序
- Promise链式调用:使用
.then()
连接多个异步操作 - async/await链式调用:使用await等待异步操作完成
6. 最佳实践与注意事项
6.1 异步编程的最佳实践
- 错误处理:始终处理异步操作中的错误,特别是在使用async/await时
- 并行操作:对于不依赖彼此的异步操作,使用Promise.all并行执行
- 取消机制:对于长时间运行的操作,实现取消机制避免资源浪费
- 避免过度使用await:对于并行操作,await会串行化执行,影响性能
6.2 性能优化建议
- 缓存结果:对于重复的异步操作,考虑缓存结果
- 限制并发:对于大量异步操作,限制并发数量避免资源耗尽
- 使用Web Workers:对于CPU密集型操作,考虑使用Web Workers
- 合理使用Promise.allSettled:当需要等待多个操作完成而不关心个别失败时
6.3 代码组织建议
- 分离异步逻辑:将异步操作封装在单独的函数中
- 使用中间件模式:对于复杂的异步流程,考虑使用中间件模式
- 避免嵌套:使用async/await减少回调嵌套
- 文档注释:为异步函数添加清晰的文档注释,说明返回的Promise状态
7 异步编程总结与未来展望
异步编程是现代Web开发的核心技能,Promise、async/await、axios和链式调用等技术共同构成了JavaScript异步编程的完整生态。这些技术各有优势,合理组合使用可以构建出高效、可维护的应用程序。
随着JavaScript语言的发展,异步编程模型将继续演进。异步迭代器、异步生成器等新特性为异步编程提供了更多可能性。同时,TypeScript等工具也为异步代码提供了更好的类型支持,进一步提高了开发效率和代码可靠性。
掌握这些异步编程技术,不仅能够解决当前的开发挑战,也为应对未来更复杂的异步场景奠定了坚实基础。通过不断实践和总结,开发者可以形成自己高效的异步编程模式,构建出更优秀的Web应用。