JavaScript异步编程:从事件循环到Promise与async/await

发布时间:2026/9/19 5:40:25
JavaScript异步编程:从事件循环到Promise与async/await
1. JavaScript异步编程的本质与价值在浏览器环境中JavaScript最初被设计为单线程语言这意味着它一次只能执行一个任务。这种设计带来了一个关键问题当遇到耗时操作如网络请求或文件读取时如果采用同步方式执行整个页面将会被冻结用户无法进行任何交互直到操作完成。这种糟糕的用户体验促使异步编程成为JavaScript的核心特性。异步编程的核心思想是非阻塞执行。当遇到需要等待的操作时JavaScript不会傻等而是将这个操作挂起继续执行后面的代码。当被挂起的操作完成后再通过回调函数、Promise或async/await等方式来处理结果。这种机制使得JavaScript能够高效地处理I/O密集型任务同时保持用户界面的流畅响应。浏览器中的典型异步场景包括网络请求fetch/XHR定时器setTimeout/setInterval用户交互事件click/input等文件读取File API数据库操作IndexedDB2. 事件循环异步编程的引擎2.1 执行模型三要素JavaScript的异步行为建立在三个核心概念上调用栈Call Stack记录函数调用的数据结构遵循后进先出原则。当函数被调用时它会被推入栈顶执行完毕后从栈顶弹出。任务队列Task Queue存放待执行的回调函数。分为两种类型宏任务队列MacroTask Queue包含setTimeout、setInterval、I/O、UI渲染等微任务队列MicroTask Queue包含Promise回调、MutationObserver等事件循环Event Loop持续运行的进程负责监控调用栈和任务队列。当调用栈为空时事件循环会从任务队列中取出最早的任务推入调用栈执行。2.2 执行优先级规则理解以下规则对编写可靠的异步代码至关重要同步代码总是最先执行直到调用栈清空然后检查微任务队列执行所有微任务包括新加入的当微任务队列清空后执行一个宏任务重复上述过程console.log(脚本开始); // 1. 同步代码 setTimeout(() { console.log(setTimeout); // 4. 宏任务 }, 0); Promise.resolve() .then(() { console.log(Promise 1); // 3. 微任务 }) .then(() { console.log(Promise 2); // 也是微任务 }); console.log(脚本结束); // 2. 同步代码 // 输出顺序 // 脚本开始 // 脚本结束 // Promise 1 // Promise 2 // setTimeout2.3 为什么需要微任务微任务的引入主要是为了在浏览器重新渲染前完成高优先级的更新。例如Promise回调需要尽快执行以保证一致性MutationObserver需要观察DOM变化其他需要在渲染前完成的任务这种设计确保了关键更新能够及时处理而不会因为等待渲染帧而被延迟。3. 回调函数异步编程的基础3.1 回调的本质与类型回调函数是JavaScript中实现异步编程最基础的方式。其核心思想是将函数作为参数传递在适当的时候调用它。回调可以分为两类同步回调立即执行不涉及任何等待常见于数组方法map、filter等会阻塞后续代码执行// 典型的同步回调 const numbers [1, 2, 3]; const doubled numbers.map(num num * 2); // 回调立即执行doubled立即得到结果[2, 4, 6]异步回调延迟执行通常用于I/O操作不会阻塞后续代码通过事件循环调度// 典型的异步回调 setTimeout(() { console.log(这段代码稍后执行); }, 1000); console.log(这段代码立即执行);3.2 回调地狱与解决方案当多个异步操作需要顺序执行时回调嵌套会导致代码难以维护getUser(userId, (user) { getOrders(user.id, (orders) { getOrderDetails(orders[0].id, (details) { updateInventory(details.productId, () { // 更多嵌套... }); }); }); });解决方案包括命名函数将匿名回调提取为命名函数Promise链使用then()方法扁平化嵌套async/await用同步写法处理异步代码4. Promise异步编程的标准化方案4.1 Promise的三态生命周期pending待定初始状态既未完成也未拒绝fulfilled已兑现操作成功完成rejected已拒绝操作失败状态转换是不可逆的一旦从pending变为fulfilled或rejected就不能再改变。4.2 Promise的核心方法4.2.1 构造函数const promise new Promise((resolve, reject) { // 执行器函数同步执行 try { const result doSomething(); resolve(result); // 转为fulfilled } catch (error) { reject(error); // 转为rejected } });4.2.2 then()方法promise.then( value { // fulfillment处理 console.log(成功:, value); return processedValue; // 可以返回新值或Promise }, error { // rejection处理可选 console.error(失败:, error); throw error; // 可以继续传递错误 } );4.2.3 catch()与finally()promise .then(handleSuccess) .catch(error { // 捕获前面所有rejection console.error(捕获错误:, error); }) .finally(() { // 无论成功失败都会执行 console.log(清理工作); });4.3 Promise的静态方法4.3.1 Promise.all()const promises [ fetch(/api/users), fetch(/api/products), fetch(/api/orders) ]; Promise.all(promises) .then(([users, products, orders]) { // 所有Promise都成功时触发 console.log(全部数据:, { users, products, orders }); }) .catch(error { // 任一Promise失败时触发 console.error(某个请求失败:, error); });4.3.2 Promise.race()const timeout new Promise((_, reject) setTimeout(() reject(new Error(超时)), 5000) ); Promise.race([fetch(/api/data), timeout]) .then(data { // 先完成的Promise决定结果 console.log(及时获取数据:, data); }) .catch(error { console.error(请求超时或失败:, error); });4.3.3 Promise.allSettled()Promise.allSettled([ Promise.resolve(成功), Promise.reject(失败), Promise.resolve(另一个成功) ]).then(results { results.forEach(result { if (result.status fulfilled) { console.log(成功:, result.value); } else { console.log(失败:, result.reason); } }); });5. async/await同步风格的异步编程5.1 基本用法async function fetchUserData() { try { const user await fetch(/api/users/1); const orders await fetch(/api/orders?userId${user.id}); return { user, orders }; } catch (error) { console.error(获取数据失败:, error); throw error; // 可以重新抛出 } } // 调用async函数返回Promise fetchUserData() .then(data console.log(用户数据:, data)) .catch(error console.error(最终错误:, error));5.2 并行执行优化async function fetchAllData() { // 顺序执行效率低 const user await fetchUser(); const orders await fetchOrders(); const settings await fetchSettings(); // 并行执行推荐 const [user, orders, settings] await Promise.all([ fetchUser(), fetchOrders(), fetchSettings() ]); return { user, orders, settings }; }5.3 错误处理模式// 方法1try/catch async function method1() { try { const data await fetchData(); return process(data); } catch (error) { console.error(处理失败:, error); return fallbackValue; } } // 方法2Promise.catch() async function method2() { const data await fetchData().catch(error { console.error(获取失败:, error); return defaultData; }); return process(data); } // 方法3高阶函数封装 function withErrorHandling(fn) { return async (...args) { try { return await fn(...args); } catch (error) { console.error(执行失败:, error); return null; } }; } const safeFetch withErrorHandling(fetchData);6. 实战技巧与性能优化6.1 取消异步操作function createCancelablePromise(promise) { let isCanceled false; const wrappedPromise new Promise((resolve, reject) { promise .then(value { if (!isCanceled) resolve(value); }) .catch(error { if (!isCanceled) reject(error); }); }); return { promise: wrappedPromise, cancel() { isCanceled true; } }; } // 使用示例 const { promise, cancel } createCancelablePromise(fetch(/api/data)); // 用户取消请求 cancelButton.addEventListener(click, cancel);6.2 请求竞速与超时async function fetchWithTimeout(url, options {}, timeout 5000) { const controller new AbortController(); const timeoutId setTimeout(() controller.abort(), timeout); try { const response await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeoutId); return response; } catch (error) { if (error.name AbortError) { throw new Error(请求超时 (${timeout}ms)); } throw error; } }6.3 并发控制async function parallelWithConcurrency(tasks, concurrency 5) { const results []; const executing new Set(); for (const task of tasks) { if (executing.size concurrency) { await Promise.race(executing); } const promise task().then(result { executing.delete(promise); return result; }); executing.add(promise); results.push(promise); } return Promise.all(results); } // 使用示例 const urls [...Array(20)].map((_, i) https://example.com/data/${i}); const tasks urls.map(url () fetch(url).then(r r.json())); parallelWithConcurrency(tasks, 3) .then(data console.log(全部完成:, data));7. 常见问题与调试技巧7.1 Promise未被捕获的rejection// 全局捕获未处理的Promise rejection process.on(unhandledRejection, (reason, promise) { console.error(未处理的 rejection:, reason); // 可以在这里记录错误或优雅退出 }); // 或者在浏览器中 window.addEventListener(unhandledrejection, event { console.error(未处理的 rejection:, event.reason); event.preventDefault(); // 阻止默认错误输出 });7.2 async函数中的常见陷阱// 陷阱1忘记await async function trap1() { const promise fetchData(); // 忘记await console.log(promise); // 输出Promise对象而非结果 } // 陷阱2不必要的顺序执行 async function trap2() { const a await getA(); // 等待 const b await getB(); // 不必要地等待 // 应该并行执行 const [a, b] await Promise.all([getA(), getB()]); } // 陷阱3错误处理遗漏 async function trap3() { try { return await fetchData(); } catch (error) { console.error(error); // 忘记返回错误状态或默认值 } }7.3 性能分析工具// 使用console.time测量异步操作耗时 async function measurePerformance() { console.time(数据获取); const data await fetchData(); console.timeEnd(数据获取); // 输出耗时 // 使用performance.mark进行更精细测量 performance.mark(process-start); await processData(data); performance.mark(process-end); performance.measure(数据处理, process-start, process-end); console.log(performance.getEntriesByName(数据处理)); }8. 现代JavaScript异步模式8.1 Top-level await// 在模块顶层直接使用await const data await fetch(/api/config); console.log(应用配置:, data); // 注意只能在ES模块中使用不能在普通脚本中8.2 Promise组合模式// 重试机制 function withRetry(fn, retries 3, delay 1000) { return async function (...args) { let lastError; for (let i 0; i retries; i) { try { return await fn(...args); } catch (error) { lastError error; if (i retries - 1) { await new Promise(r setTimeout(r, delay)); } } } throw lastError; }; } // 超时重试组合 const reliableFetch withRetry( url fetchWithTimeout(url, {}, 3000), 3, 1000 );8.3 Web Workers与异步// 主线程 const worker new Worker(worker.js); // 使用Promise封装Worker通信 function runInWorker(task, data) { return new Promise((resolve, reject) { const id Math.random().toString(36).slice(2); worker.postMessage({ id, task, data }); const handler ({ data: result }) { if (result.id id) { worker.removeEventListener(message, handler); if (result.error) { reject(new Error(result.error)); } else { resolve(result.data); } } }; worker.addEventListener(message, handler); }); } // worker.js self.addEventListener(message, async ({ data }) { try { const result await processTask(data.task, data.data); self.postMessage({ id: data.id, data: result }); } catch (error) { self.postMessage({ id: data.id, error: error.message }); } });在实际项目中选择异步编程方案时应考虑代码可读性与维护性错误处理需求性能要求团队熟悉程度浏览器/Node.js版本支持对于新项目推荐优先使用async/await结合Promise.all等组合方法既能保证代码清晰又能充分利用现代JavaScript的异步能力。