在 JavaScript 中,代码默认是同步执行的,即按顺序逐行执行。不过,处理一些耗时操作时(如 API 请求或文件读取),同步代码可能会造成页面卡顿。这时,异步编程就派上用场了。
1. 回调函数
1.1 基本概念
回调函数是异步编程的最基础形式。它是一个函数,在任务完成后执行。将回调函数作为参数传递给另一个函数,使得任务完成时可以执行特定的代码。
1 2 3 4 5 6 7 8 9 10
| function fetchData(callback) { setTimeout(() => { console.log("数据获取完毕"); callback(); }, 2000); }
fetchData(() => { console.log("处理数据..."); });
|
1.2 回调地狱
当任务依赖于多个异步操作时,回调函数会嵌套得越来越深,导致代码难以阅读和维护。这种现象被称为 回调地狱。
1 2 3 4 5 6 7 8 9
| fetchData(() => { console.log("Step 1 complete"); fetchData(() => { console.log("Step 2 complete"); fetchData(() => { console.log("Step 3 complete"); }); }); });
|
2. Promise
2.1 什么是 Promise
Promise
是 ES6 引入的一种处理异步操作的对象。它有三种状态:
- Pending(待定):初始状态,任务尚未完成。
- Fulfilled(已完成):任务成功,返回结果。
- Rejected(已拒绝):任务失败,返回错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| let promise = new Promise((resolve, reject) => { let success = true; if (success) { resolve("任务成功"); } else { reject("任务失败"); } });
promise.then(result => { console.log(result); }).catch(error => { console.log(error); });
|
2.2 使用链式 Promise
可以使用 .then()
链式调用多个 Promise,从而避免回调地狱问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function step1() { return new Promise((resolve) => { setTimeout(() => { console.log("Step 1 complete"); resolve(); }, 1000); }); }
function step2() { return new Promise((resolve) => { setTimeout(() => { console.log("Step 2 complete"); resolve(); }, 1000); }); }
step1().then(step2).then(() => { console.log("All steps complete"); });
|
2.3 Promise.all 和 Promise.race
- Promise.all:等待所有 Promise 完成,返回一个包含所有结果的数组。
- Promise.race:只要一个 Promise 完成,就返回该 Promise 的结果。
1 2 3 4 5 6 7 8 9 10
| let p1 = new Promise((resolve) => setTimeout(resolve, 1000, "P1 done")); let p2 = new Promise((resolve) => setTimeout(resolve, 2000, "P2 done"));
Promise.all([p1, p2]).then(results => { console.log(results); });
Promise.race([p1, p2]).then(result => { console.log(result); });
|
3. async/await
3.1 基本概念
async/await
是基于 Promise 的语法糖,让异步代码看起来更像同步代码。async
函数返回一个 Promise,而 await
关键字用于等待一个 Promise 完成,只有在 async
函数中才能使用 await
。
1 2 3 4 5 6 7
| async function fetchData() { let response = await fetch("https://api.example.com/data"); let data = await response.json(); console.log(data); }
fetchData();
|
3.2 使用 async/await 处理多个异步任务
可以使用 await
来处理多个异步操作,代码看起来更加直观:
1 2 3 4 5 6 7
| async function processSteps() { await step1(); await step2(); console.log("All steps complete"); }
processSteps();
|
3.3 处理错误
在 async/await
中可以使用 try...catch
来捕获错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| async function fetchData() { try { let response = await fetch("https://api.example.com/data"); if (!response.ok) { throw new Error("数据获取失败"); } let data = await response.json(); console.log(data); } catch (error) { console.error(error.message); } }
fetchData();
|
4. JavaScript 中的事件循环
4.1 什么是事件循环
JavaScript 是单线程语言,它的异步操作通过 事件循环 来实现。事件循环的核心概念是任务队列(Task Queue)和调用栈(Call Stack)。当调用栈为空时,事件循环会从任务队列中取出任务并将其压入调用栈中执行。
4.2 微任务与宏任务
异步任务分为 宏任务 和 微任务。setTimeout
、setInterval
属于宏任务,而 Promise.then
、process.nextTick
属于微任务。
在事件循环中,每个宏任务结束后,都会先检查并执行所有的微任务,才会进入下一个宏任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| console.log("start");
setTimeout(() => { console.log("macro task"); }, 0);
Promise.resolve().then(() => { console.log("micro task"); });
console.log("end");
|