进阶-同步与异步
约 1662 字大约 6 分钟
2025-10-10
同步与异步
首先,要认识开发过程中一个很重要的概念:同步与异步
同步
任务一个接一个地执行。后一个任务必须等待前一个任务完全结束之后才能开始。特点是:
顺序执行:代码按照书写顺序一行一行地执行。
阻塞:在执行一个耗时操作(如读取文件、网络请求)时,整个程序会“卡住”,等待这个操作完成,期间不能做任何其他事情。
易于理解:代码流程是线性的,符合人类的直觉思维。
异步
发起一个耗时任务后,不必等待它完成,可以立刻继续执行后面的代码。当那个耗时任务完成后,会通过一种机制(如回调函数、Promise、事件)来通知你,并处理结果。特点:
非阻塞:发起耗时操作后,程序不会被“卡住”,可以继续处理其他任务。
高效:特别适合I/O密集型操作(如网络请求、磁盘读写),能充分利用CPU时间,避免空等。
复杂度高:代码流程不是线性的,管理和调试相对困难。
举个生活中的例子:
同步就是在超市收银台排队,你必须等前面的人全部结账完毕,才能轮到你。
异步就是在餐厅点餐。你点完餐后(发起任务),不需要站在厨房门口等,可以回座位玩手机(执行其他任务)。餐好后,服务员会叫你(回调通知)。
JavaScript 是一门事件驱动和单线程的语言。这意味着:
单线程: 它一次只能做一件事。
非阻塞: 为了避免在执行一个耗时操作(如网络请求、读取文件)时“卡死”整个程序,JavaScript 大量使用了异步编程。
以下是JavaScript中异步机制的演进历程:
回调函数
回调函数简单来说就是:
如果 A 函数作为 B 函数的参数,那么 A 函数就是回调函数,B 函数通常被叫做高阶函数或外部函数
回调函数支持两种调用方式:
- 同步调用(同步回调)
- 异步调用(异步回调)
同步回调
同步回调提供了确定的、顺序的执行保证,这在需要明确函数先后执行顺序的场景中非常实用:
执行顺序的保证
// 高阶函数 - 接收一个回调函数 function sayHello(callback) { console.log('先说:你好!'); callback(); // 在这里调用回调函数 console.log('最后说:再见!'); } // 定义回调函数 function myCallback() { console.log('回调说:今天天气真好!'); } // 使用 sayHello(myCallback);输出结果:
先说:你好! 回调说:今天天气真好! 最后说:再见!数据传递的确定性
// 1. 先检查钱够不够 function checkMoney(money, callback) { console.log('检查余额:', money + '元'); if (money >= 10) { callback(true); // 有钱,继续买 } else { callback(false); // 没钱,结束 } } // 2. 再买东西 function buyItem(canBuy) { if (canBuy) { console.log('✅ 有钱!开始买冰淇淋...'); console.log('🍦 买到冰淇淋了!'); } else { console.log('❌ 钱不够,买不了冰淇淋'); } } // 使用:明确的先后顺序 console.log('=== 第一次:钱够的情况 ==='); checkMoney(15, buyItem); // 先检查钱,再决定买不买 console.log('\n=== 第二次:钱不够的情况 ==='); checkMoney(5, buyItem); // 先检查钱,再决定买不买输出结果:
=== 第一次:钱够的情况 === 检查余额: 15元 ✅ 有钱!开始买冰淇淋... 🍦 买到冰淇淋了! === 第二次:钱不够的情况 === 检查余额: 5元 ❌ 钱不够,买不了冰淇淋
异步回调
异步回调是指回调函数不会立即执行,而是在未来某个时间点(当某个异步操作完成时)才被调用的回调函数。
核心特征:不阻塞代码执行
console.log('第1步:开始'); setTimeout(function() { console.log('第3步:异步回调执行'); // 不会立即执行! }, 1000); console.log('第2步:结束'); // 输出顺序: // 第1步:开始 // 第2步:结束 // (等待1秒后) // 第3步:异步回调执行回调函数的参数约定:
在 Node.js 和很多 JavaScript 库中,异步回调通常遵循一个约定:
function callback(error, data) { // error: 错误信息(如果操作失败) // data: 成功返回的数据(如果操作成功) }举个例子:
function getUserInfo(userId, callback) { console.log(`正在查询用户 ${userId} 的信息...`); setTimeout(() => { // 模拟数据库查询 if (userId === 123) { // 用户存在 - 成功 const userData = { name: '小明', age: 20, email: 'xiaoming@example.com' }; callback(null, userData); // 错误为null,返回数据 } else { // 用户不存在 - 失败 const error = new Error('用户不存在'); callback(error, null); // 返回错误,数据为null } }, 1000); } getUserInfo(123, (err, user) => { if (err) { console.log('查询失败:', err.message); } else { console.log('用户信息:', user); // 输出: 用户信息: {name: '小明', age: 20, email: 'xiaoming@example.com'} } });异步回调的实际应用场景:
- 定时器
console.log('开始做饭'); setTimeout(() => { console.log('🍚 饭煮好了!'); // 5秒后才执行 }, 5000); console.log('我可以先去洗菜'); // 不会等待5秒 // 输出: // 开始做饭 // 我可以先去洗菜 // (5秒后) // 🍚 饭煮好了!- 事件监听
// 假设页面上有一个按钮 const button = document.getElementById('myButton'); console.log('程序已启动,等待点击'); button.addEventListener('click', () => { console.log('按钮被点击了!'); // 未来用户点击时才执行 }); console.log('程序继续运行...'); // 输出: // 程序已启动,等待点击 // 程序继续运行... // (当用户点击按钮时) // 按钮被点击了!- 网络请求
console.log('开始请求数据'); // 模拟网络请求 function requestData(callback) { console.log('请求发送中...'); setTimeout(() => { const data = { name: '小明', age: 18 }; callback(data); // 2秒后收到数据才执行回调 }, 2000); } // 使用回调函数处理数据 requestData((result) => { console.log('收到数据:', result); }); console.log('请求已发送,我可以做其他事情'); // 输出: // 开始请求数据 // 请求发送中... // 请求已发送,我可以做其他事情 // (2秒后) // 收到数据: {name: '小明', age: 18}- 文件读取模拟
function readFile(filename, callback) { console.log(`开始读取文件: ${filename}`); // 模拟读取过程 setTimeout(() => { const content = `这是${filename}的内容`; callback(null, content); // 模拟读取成功 }, 1500); } console.log('程序启动'); readFile('test.txt', (error, data) => { if (error) { console.log('读取失败:', error); } else { console.log('文件内容:', data); } }); console.log('我可以继续处理其他任务'); // 输出: // 程序启动 // 开始读取文件: test.txt // 我可以继续处理其他任务 // (1.5秒后) // 文件内容: 这是test.txt的内容
