目标:掌握 JavaScript 异步编程的核心概念,包括事件循环、Promise、async/await,以及各种异步模式和最佳实践。
目录
同步与异步、事件循环
异步方案逐项实战
高级异步模式
实战应用场景
代码片段速查
常见问题与解决方案
速记与总结
1. 同步与异步、事件循环 1.1 同步 vs 异步 理解同步和异步的区别是掌握 JavaScript 异步编程的基础。
1.1.1 同步执行(Synchronous) 同步代码按照书写顺序一行一行执行,前一行执行完成后才会执行下一行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 console .log ('1. 开始' );console .log ('2. 中间' );console .log ('3. 结束' );console .log ('开始计算' );let sum = 0 ;for (let i = 0 ; i < 1000000000 ; i++) { sum += i; } console .log ('计算完成:' , sum); console .log ('后续代码' );
同步的特点 :
✅ 执行顺序可预测
✅ 代码逻辑清晰
❌ 阻塞后续代码执行
❌ 长时间操作会导致页面卡顿
1.1.2 异步执行(Asynchronous) 异步代码不会阻塞后续代码的执行,任务会在后台执行,完成后通过回调函数通知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 console .log ('1. 开始' );setTimeout (() => { console .log ('2. 异步任务完成' ); }, 1000 ); console .log ('3. 结束' );console .log ('开始请求数据' );fetch ('/api/data' ) .then (response => response.json ()) .then (data => { console .log ('数据获取完成:' , data); }); console .log ('继续执行其他代码' );
异步的特点 :
✅ 不阻塞后续代码
✅ 提高用户体验
✅ 可以并发处理多个任务
❌ 执行顺序不如同步代码直观
❌ 需要回调或 Promise 处理结果
1.1.3 为什么需要异步? 场景1:网络请求
1 2 3 4 5 6 7 8 9 10 11 12 const data = fetch ('/api/data' ); console .log (data); fetch ('/api/data' ) .then (response => response.json ()) .then (data => { console .log (data); });
场景2:定时任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function waitSync (ms ) { const start = Date .now (); while (Date .now () - start < ms) { } } waitSync (1000 ); console .log ('1秒后' );setTimeout (() => { console .log ('1秒后' ); }, 1000 );
场景3:文件操作(Node.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const fs = require ('fs' );const data = fs.readFileSync ('large-file.txt' , 'utf8' ); console .log ('文件读取完成' );fs.readFile ('large-file.txt' , 'utf8' , (err, data ) => { if (err) { console .error ('读取失败:' , err); return ; } console .log ('文件读取完成:' , data); }); console .log ('继续执行其他代码' );
1.2 事件循环(Event Loop)概览 事件循环是 JavaScript 实现异步的核心机制。理解事件循环有助于理解异步代码的执行顺序。
1.2.1 JavaScript 是单线程的 JavaScript 只有一个主线程(Main Thread),所有代码都在这个线程上执行。
1 2 3 4 5 console .log ('任务1' );console .log ('任务2' );console .log ('任务3' );
为什么是单线程?
简化编程模型(不需要处理线程同步、死锁等问题)
适合处理 DOM 操作(多线程操作 DOM 会带来复杂的同步问题)
单线程的挑战 :
如何在不阻塞的情况下处理耗时操作?
答案:事件循环 + 异步 API
1.2.2 事件循环的组成部分 事件循环由以下几个部分组成:
1. 调用栈(Call Stack)
调用栈用于执行同步代码,遵循后进先出(LIFO)原则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function a ( ) { console .log ('a 开始' ); b (); console .log ('a 结束' ); } function b ( ) { console .log ('b 开始' ); c (); console .log ('b 结束' ); } function c ( ) { console .log ('c' ); } a ();
2. 宏任务队列(Macro Task Queue)
宏任务包括:
setTimeout / setInterval
I/O 操作(文件读取、网络请求)
UI 渲染
script 标签执行
1 2 3 4 5 6 7 8 9 10 11 12 console .log ('1. 同步代码' );setTimeout (() => { console .log ('2. setTimeout' ); }, 0 ); console .log ('3. 同步代码' );
3. 微任务队列(Micro Task Queue)
微任务包括:
Promise.then / catch / finally
queueMicrotask
MutationObserver
1 2 3 4 5 6 7 8 9 10 11 12 console .log ('1. 同步代码' );Promise .resolve ().then (() => { console .log ('2. Promise.then' ); }); console .log ('3. 同步代码' );
1.2.3 事件循环的执行顺序 事件循环的执行顺序可以用以下步骤概括:
1 2 3 4 5 1. 执行调用栈中的同步代码 2. 调用栈清空后,执行所有微任务 3. 执行一个宏任务 4. 再次执行所有微任务 5. 重复步骤 3-4
详细示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 console .log ('1. 同步代码' );setTimeout (() => { console .log ('2. setTimeout(宏任务)' ); }, 0 ); Promise .resolve ().then (() => { console .log ('3. Promise.then(微任务)' ); }); queueMicrotask (() => { console .log ('4. queueMicrotask(微任务)' ); }); console .log ('5. 同步代码' );
复杂示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 console .log ('1. 同步开始' );setTimeout (() => { console .log ('2. setTimeout 1' ); Promise .resolve ().then (() => { console .log ('3. Promise in setTimeout' ); }); }, 0 ); Promise .resolve ().then (() => { console .log ('4. Promise 1' ); setTimeout (() => { console .log ('5. setTimeout in Promise' ); }, 0 ); }); Promise .resolve ().then (() => { console .log ('6. Promise 2' ); }); console .log ('7. 同步结束' );
1.2.4 微任务优先于宏任务 这是事件循环的重要规则:微任务会在宏任务之前执行 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 console .log ('开始' );setTimeout (() => { console .log ('宏任务' ); }, 0 ); Promise .resolve ().then (() => { console .log ('微任务' ); }); console .log ('结束' );
为什么微任务优先?
微任务通常用于:
Promise 回调
DOM 变更通知(MutationObserver)
这些操作需要在下一个宏任务之前完成,确保状态的一致性。
1.2.5 动画与渲染 浏览器会在宏任务之间执行渲染操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 console .log ('1. 同步代码' );setTimeout (() => { console .log ('2. 宏任务1' ); }, 0 ); setTimeout (() => { console .log ('3. 宏任务2' ); }, 0 );
requestAnimationFrame
requestAnimationFrame 会在下一次重绘之前执行,通常用于动画:
1 2 3 4 5 6 7 8 function animate ( ) { element.style .left = position + 'px' ; requestAnimationFrame (animate); } requestAnimationFrame (animate);
执行时机对比 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 console .log ('1. 同步代码' );setTimeout (() => { console .log ('2. setTimeout' ); }, 0 ); Promise .resolve ().then (() => { console .log ('3. Promise' ); }); requestAnimationFrame (() => { console .log ('4. requestAnimationFrame' ); });
1.3 回调函数与回调地狱 1.3.1 什么是回调函数? 回调函数是作为参数传递给另一个函数的函数,在特定时机被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function processArray (arr, callback ) { for (let item of arr) { callback (item); } } processArray ([1 , 2 , 3 ], (item ) => { console .log (item); }); setTimeout (() => { console .log ('1秒后执行' ); }, 1000 ); button.addEventListener ('click' , () => { console .log ('按钮被点击' ); });
1.3.2 回调地狱(Callback Hell) 当多个异步操作需要串行执行时,会出现深层嵌套的回调,这就是”回调地狱”。
1 2 3 4 5 6 7 8 9 10 11 12 13 getUser (userId, (user ) => { getProfile (user.id , (profile ) => { getPosts (profile.id , (posts ) => { getComments (posts[0 ].id , (comments ) => { renderComments (comments, () => { console .log ('完成' ); }); }); }); }); });
回调地狱的问题 :
代码可读性差 :深层嵌套难以理解
错误处理困难 :每个回调都要单独处理错误
控制流分散 :逻辑分散在各个回调中
难以维护 :修改一个环节需要修改多处代码
1.3.3 缓解回调地狱的方法 方法1:拆分成命名函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 getUser (userId, (user ) => { getProfile (user.id , (profile ) => { getPosts (profile.id , (posts ) => { renderPosts (posts); }); }); }); function handleUser (user ) { getProfile (user.id , handleProfile); } function handleProfile (profile ) { getPosts (profile.id , handlePosts); } function handlePosts (posts ) { renderPosts (posts); } getUser (userId, handleUser);
方法2:使用 Promise
1 2 3 4 5 6 getUser (userId) .then (user => getProfile (user.id )) .then (profile => getPosts (profile.id )) .then (posts => renderPosts (posts)) .catch (error => console .error (error));
方法3:使用 async/await
1 2 3 4 5 6 7 8 9 10 11 async function loadData ( ) { try { const user = await getUser (userId); const profile = await getProfile (user.id ); const posts = await getPosts (profile.id ); renderPosts (posts); } catch (error) { console .error (error); } }
方法4:使用函数组合
1 2 3 4 5 6 7 8 9 10 11 12 const pipe = (...fns ) => (value ) => fns.reduce ((promise, fn ) => promise.then (fn), Promise .resolve (value)); const loadData = pipe ( getUser, user => getProfile (user.id ), profile => getPosts (profile.id ), renderPosts ); loadData (userId).catch (console .error );
2. 异步方案逐项实战 2.1 定时器:setTimeout / setInterval 2.1.1 setTimeout 基础 setTimeout 用于在指定时间后执行一次函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 setTimeout (callback, delay, ...args);setTimeout (() => { console .log ('1秒后执行' ); }, 1000 ); setTimeout ((name, age ) => { console .log (`姓名: ${name} , 年龄: ${age} ` ); }, 1000 , '张三' , 25 ); const timerId = setTimeout (() => { console .log ('这行不会执行' ); }, 1000 ); clearTimeout (timerId);
重要特性 :
最小延时 :在浏览器中,setTimeout 的最小延时约为 4ms(即使设置为 0)
1 2 3 4 5 6 7 8 9 10 console .log ('开始' );setTimeout (() => { console .log ('延时0ms' ); }, 0 ); console .log ('结束' );
不保证精确时间 :setTimeout 只保证至少 在指定时间后执行,不保证精确
1 2 3 4 5 const start = Date .now ();setTimeout (() => { const elapsed = Date .now () - start; console .log (`实际延时: ${elapsed} ms` ); }, 1000 );
页面后台时会被节流 :当标签页在后台时,浏览器会降低定时器的执行频率
1 2 3 4 setInterval (() => { console .log ('每秒执行' ); }, 1000 );
2.1.2 setInterval 基础 setInterval 用于每隔指定时间重复执行函数。
1 2 3 4 5 6 7 8 9 10 11 12 setInterval (callback, delay, ...args);const intervalId = setInterval (() => { console .log ('每秒执行' ); }, 1000 ); setTimeout (() => { clearInterval (intervalId); }, 5000 );
setInterval 的问题 :
时间漂移 :如果回调执行时间超过间隔时间,会导致时间漂移
1 2 3 4 5 setInterval (() => { process (); }, 500 );
重入问题 :如果回调执行时间超过间隔,可能会同时执行多个回调
1 2 3 4 5 setInterval (() => { process (); }, 500 );
解决方案:使用递归 setTimeout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function repeat (callback, delay ) { const timerId = setTimeout (() => { callback (); repeat (callback, delay); }, delay); return timerId; } let count = 0 ;const timerId = repeat (() => { console .log (count++); if (count >= 5 ) { clearTimeout (timerId); } }, 1000 );
更完善的实现 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function setIntervalFixed (callback, delay ) { let timerId; function schedule ( ) { timerId = setTimeout (() => { callback (); schedule (); }, delay); } schedule (); return () => clearTimeout (timerId); } const cancel = setIntervalFixed (() => { console .log ('执行' ); }, 1000 ); setTimeout (cancel, 5000 );
2.1.3 实际应用场景 场景1:防抖(Debounce)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function debounce (fn, delay ) { let timerId = null ; return function (...args ) { clearTimeout (timerId); timerId = setTimeout (() => { fn.apply (this , args); }, delay); }; } const searchInput = document .getElementById ('search' );const debouncedSearch = debounce ((query ) => { console .log ('搜索:' , query); }, 300 ); searchInput.addEventListener ('input' , (e ) => { debouncedSearch (e.target .value ); });
场景2:节流(Throttle)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function throttle (fn, delay ) { let lastTime = 0 ; return function (...args ) { const now = Date .now (); if (now - lastTime >= delay) { lastTime = now; fn.apply (this , args); } }; } const throttledScroll = throttle (() => { console .log ('滚动' ); }, 100 ); window .addEventListener ('scroll' , throttledScroll);
场景3:延迟执行
1 2 3 4 5 6 7 8 9 10 11 12 13 function showTooltip (element, message ) { const timerId = setTimeout (() => { element.textContent = message; element.style .display = 'block' ; }, 500 ); element.addEventListener ('mouseleave' , () => { clearTimeout (timerId); element.style .display = 'none' ; }); }
2.2 Promise 基础与链式调用 2.2.1 Promise 的三种状态 Promise 有三种状态,状态转换是单向的:
1 pending(等待) → fulfilled(成功)或 rejected(失败)
1 2 3 4 5 6 7 8 9 10 11 12 const promise = new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('成功' ); }, 1000 ); }); console .log (promise);
状态特点 :
状态不可逆 :一旦状态改变,就不能再改变
1 2 3 4 5 6 7 8 const promise = new Promise ((resolve, reject ) => { resolve ('成功' ); reject ('失败' ); }); promise.then (value => { console .log (value); });
pending → fulfilled :调用 resolve(value)
1 2 3 4 5 6 7 const promise = new Promise ((resolve ) => { resolve ('成功值' ); }); promise.then (value => { console .log (value); });
pending → rejected :调用 reject(reason) 或抛出错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const promise1 = new Promise ((resolve, reject ) => { reject ('失败原因' ); }); const promise2 = new Promise (() => { throw new Error ('错误' ); }); promise1.catch (reason => { console .log (reason); }); promise2.catch (error => { console .log (error.message ); });
2.2.2 Promise 的链式调用 then 和 catch 方法返回新的 Promise,可以链式调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 Promise .resolve (1 ) .then (value => { console .log (value); return value * 2 ; }) .then (value => { console .log (value); return value * 3 ; }) .then (value => { console .log (value); });
返回值处理 :
返回普通值 :会被包装成 fulfilled Promise
1 2 3 4 5 Promise .resolve (1 ) .then (value => value * 2 ) .then (value => { console .log (value); });
返回 Promise :会等待这个 Promise 完成
1 2 3 4 5 6 7 8 9 Promise .resolve (1 ) .then (value => { return new Promise (resolve => { setTimeout (() => resolve (value * 2 ), 1000 ); }); }) .then (value => { console .log (value); });
抛出错误 :会被包装成 rejected Promise
1 2 3 4 5 6 7 Promise .resolve (1 ) .then (value => { throw new Error ('错误' ); }) .catch (error => { console .log (error.message ); });
实际应用示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 fetch ('/api/user' ) .then (response => response.json ()) .then (user => { console .log ('用户:' , user); return fetch (`/api/profile/${user.id} ` ); }) .then (response => response.json ()) .then (profile => { console .log ('资料:' , profile); }) .catch (error => { console .error ('错误:' , error); });
2.2.3 错误冒泡 Promise 链中的错误会向下传播,直到被 catch 捕获。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Promise .resolve (1 ) .then (value => { throw new Error ('错误1' ); }) .then (value => { console .log ('这行不会执行' ); }) .catch (error => { console .log ('捕获错误:' , error.message ); return '恢复值' ; }) .then (value => { console .log (value); });
错误处理策略 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 fetch ('/api/data' ) .then (response => response.json ()) .then (data => processData (data)) .catch (error => { console .error ('操作失败:' , error); }); fetch ('/api/data' ) .then (response => { if (!response.ok ) { throw new Error ('网络错误' ); } return response.json (); }) .catch (error => { if (error.message === '网络错误' ) { return { data : [] }; } throw error; }) .then (data => { console .log (data); }) .catch (error => { console .error ('其他错误:' , error); });
2.2.4 finally 方法 finally 方法无论 Promise 是成功还是失败都会执行。
1 2 3 4 5 6 7 8 9 10 11 12 fetch ('/api/data' ) .then (response => response.json ()) .then (data => { console .log ('成功:' , data); }) .catch (error => { console .error ('失败:' , error); }) .finally (() => { console .log ('无论成功失败都会执行' ); });
finally 的特点 :
返回值会被忽略 (除非抛出错误)
1 2 3 4 5 6 7 8 Promise .resolve (1 ) .then (value => value * 2 ) .finally (() => { return 999 ; }) .then (value => { console .log (value); });
如果 finally 中抛出错误,会覆盖之前的返回值
1 2 3 4 5 6 7 8 9 10 11 Promise .resolve (1 ) .then (value => value * 2 ) .finally (() => { throw new Error ('finally 错误' ); }) .then (value => { console .log ('不会执行' ); }) .catch (error => { console .log (error.message ); });
2.3 async/await async/await 是 Promise 的语法糖,让异步代码看起来像同步代码。
2.3.1 async 函数 async 函数总是返回 Promise。
1 2 3 4 5 6 7 8 9 10 11 12 async function getData ( ) { return '数据' ; } const promise = getData ();console .log (promise); function getData ( ) { return Promise .resolve ('数据' ); }
async 函数的返回值 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 async function getValue ( ) { return 42 ; } getValue ().then (value => console .log (value)); async function getPromise ( ) { return Promise .resolve ('值' ); } getPromise ().then (value => console .log (value)); async function throwError ( ) { throw new Error ('错误' ); } throwError ().catch (error => console .log (error.message ));
2.3.2 await 关键字 await 等待 Promise 完成,只能在 async 函数中使用(或顶层 ES 模块)。
1 2 3 4 5 6 7 8 9 10 11 12 async function fetchData ( ) { const response = await fetch ('/api/data' ); const data = await response.json (); return data; } function fetchData ( ) { return fetch ('/api/data' ) .then (response => response.json ()); }
await 的行为 :
等待 Promise 完成 :如果 Promise 是 pending,会暂停函数执行
1 2 3 4 5 6 7 8 9 10 11 async function test ( ) { console .log ('开始' ); await new Promise (resolve => setTimeout (resolve, 1000 )); console .log ('1秒后' ); } test ();
返回 resolved 值 :如果 Promise fulfilled,返回其值
1 2 3 4 async function test ( ) { const value = await Promise .resolve (42 ); console .log (value); }
抛出 rejected 值 :如果 Promise rejected,抛出错误
1 2 3 4 5 6 7 async function test ( ) { try { await Promise .reject ('错误' ); } catch (error) { console .log (error); } }
2.3.3 串行 vs 并行 串行执行 (一个接一个):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 async function serial ( ) { const a = await taskA (); const b = await taskB (); return [a, b]; } async function parallel ( ) { const [a, b] = await Promise .all ([ taskA (), taskB () ]); return [a, b]; }
实际示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 async function loadUserAndPosts (userId ) { const user = await fetch (`/api/users/${userId} ` ); const userData = await user.json (); const posts = await fetch (`/api/users/${userId} /posts` ); const postsData = await posts.json (); return { user : userData, posts : postsData }; } async function loadUserAndPostsParallel (userId ) { const [userRes, postsRes] = await Promise .all ([ fetch (`/api/users/${userId} ` ), fetch (`/api/users/${userId} /posts` ) ]); const [userData, postsData] = await Promise .all ([ userRes.json (), postsRes.json () ]); return { user : userData, posts : postsData }; }
有依赖关系的串行 :
1 2 3 4 5 6 async function loadData (userId ) { const user = await fetchUser (userId); const posts = await fetchPosts (user.id ); return { user, posts }; }
2.3.4 错误处理 方法1:try-catch
1 2 3 4 5 6 7 8 9 10 11 12 13 async function fetchData ( ) { try { const response = await fetch ('/api/data' ); if (!response.ok ) { throw new Error ('请求失败' ); } const data = await response.json (); return data; } catch (error) { console .error ('获取数据失败:' , error); throw error; } }
方法2:catch 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 async function fetchData ( ) { const response = await fetch ('/api/data' ) .catch (error => { console .error ('网络错误:' , error); return null ; }); if (!response) { return null ; } return response.json (); }
方法3:封装安全函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 async function safe (promise ) { try { const data = await promise; return [null , data]; } catch (error) { return [error, null ]; } } const [error, data] = await safe (fetch ('/api/data' ));if (error) { console .error ('错误:' , error); } else { console .log ('数据:' , data); }
2.4 并发工具:Promise.all / race / allSettled / any 2.4.1 Promise.all Promise.all 等待所有 Promise 完成,如果有一个失败,整体失败。
1 2 3 4 5 6 7 8 Promise .all ([promise1, promise2, ...]) .then ([value1, value2, ...] => { }) .catch (error => { });
特点 :
全部成功才成功 :所有 Promise 都 fulfilled 时,返回值的数组
1 2 3 4 5 6 7 Promise .all ([ Promise .resolve (1 ), Promise .resolve (2 ), Promise .resolve (3 ) ]).then (values => { console .log (values); });
一个失败就失败 :任何一个 Promise rejected,整体 rejected
1 2 3 4 5 6 7 Promise .all ([ Promise .resolve (1 ), Promise .reject ('错误' ), Promise .resolve (3 ) ]).catch (error => { console .log (error); });
返回值顺序 :返回值的顺序与输入顺序一致
1 2 3 4 5 6 7 Promise .all ([ delay (100 , 'a' ), delay (50 , 'b' ), delay (200 , 'c' ) ]).then (values => { console .log (values); });
实际应用 :
1 2 3 4 5 6 7 8 9 10 async function loadDashboard ( ) { const [users, posts, comments] = await Promise .all ([ fetch ('/api/users' ).then (r => r.json ()), fetch ('/api/posts' ).then (r => r.json ()), fetch ('/api/comments' ).then (r => r.json ()) ]); return { users, posts, comments }; }
2.4.2 Promise.race Promise.race 返回第一个完成的 Promise(无论成功还是失败)。
1 2 3 4 5 6 7 8 Promise .race ([promise1, promise2, ...]) .then (value => { }) .catch (error => { });
特点 :
第一个完成就返回 :无论是 fulfilled 还是 rejected
1 2 3 4 5 6 7 Promise .race ([ delay (100 , 'a' ), delay (50 , 'b' ), delay (200 , 'c' ) ]).then (value => { console .log (value); });
如果第一个是 rejected,整体 rejected
1 2 3 4 5 6 Promise .race ([ Promise .reject ('错误' ), delay (100 , '成功' ) ]).catch (error => { console .log (error); });
实际应用:超时控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function withTimeout (promise, timeoutMs ) { const timeout = new Promise ((_, reject ) => setTimeout (() => reject (new Error ('超时' )), timeoutMs) ); return Promise .race ([promise, timeout]); } withTimeout (fetch ('/api/data' ), 5000 ) .then (response => response.json ()) .catch (error => { if (error.message === '超时' ) { console .error ('请求超时' ); } else { console .error ('其他错误:' , error); } });
2.4.3 Promise.allSettled Promise.allSettled 等待所有 Promise 完成(无论成功还是失败),返回每个 Promise 的结果。
1 2 3 4 5 Promise .allSettled ([promise1, promise2, ...]) .then (results => { });
返回值格式 :
1 2 3 4 5 6 7 8 9 10 11 12 Promise .allSettled ([ Promise .resolve (1 ), Promise .reject ('错误' ), Promise .resolve (3 ) ]).then (results => { console .log (results); });
实际应用:部分成功处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 async function loadMultipleUsers (userIds ) { const promises = userIds.map (id => fetch (`/api/users/${id} ` ) .then (r => r.json ()) .catch (error => ({ error, id })) ); const results = await Promise .allSettled (promises); const users = []; const errors = []; results.forEach ((result, index ) => { if (result.status === 'fulfilled' ) { users.push (result.value ); } else { errors.push ({ userId : userIds[index], error : result.reason }); } }); return { users, errors }; }
2.4.4 Promise.any Promise.any 返回第一个成功的 Promise,如果全部失败,抛出 AggregateError。
1 2 3 4 5 6 7 8 Promise .any ([promise1, promise2, ...]) .then (value => { }) .catch (error => { });
特点 :
第一个成功就返回
1 2 3 4 5 6 7 Promise .any ([ Promise .reject ('错误1' ), delay (100 , '成功' ), Promise .reject ('错误2' ) ]).then (value => { console .log (value); });
全部失败才失败
1 2 3 4 5 6 7 8 Promise .any ([ Promise .reject ('错误1' ), Promise .reject ('错误2' ), Promise .reject ('错误3' ) ]).catch (error => { console .log (error instanceof AggregateError ); console .log (error.errors ); });
实际应用:多服务器容错
1 2 3 4 5 6 7 8 9 10 11 async function fetchFromMultipleServers (urls ) { const promises = urls.map (url => fetch (url)); try { const response = await Promise .any (promises); return await response.json (); } catch (error) { console .error ('所有服务器都失败:' , error.errors ); throw new Error ('无法连接到任何服务器' ); } }
四种方法的对比 :
方法
成功条件
失败条件
返回值
Promise.all
全部成功
一个失败
值数组
Promise.race
第一个完成
第一个失败
第一个值
Promise.allSettled
全部完成
不会失败
结果数组
Promise.any
一个成功
全部失败
第一个成功值
2.5 异步错误处理 2.5.1 Promise 链的错误处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 fetch ('/api/data' ) .then (response => response.json ()) .then (data => processData (data)) .catch (error => { console .error ('操作失败:' , error); }); fetch ('/api/data' ) .then (response => { if (!response.ok ) { throw new Error ('网络错误' ); } return response.json (); }) .catch (error => { if (error.message === '网络错误' ) { return { data : [] }; } throw error; }) .then (data => { console .log (data); }) .catch (error => { console .error ('其他错误:' , error); });
2.5.2 async/await 的错误处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 async function fetchData ( ) { try { const response = await fetch ('/api/data' ); const data = await response.json (); return data; } catch (error) { console .error ('获取数据失败:' , error); throw error; } } async function safe (promise ) { try { const data = await promise; return [null , data]; } catch (error) { return [error, null ]; } } const [error, data] = await safe (fetch ('/api/data' ));if (error) { console .error ('错误:' , error); } else { console .log ('数据:' , data); }
2.5.3 全局错误处理 浏览器环境 :
1 2 3 4 5 6 7 8 9 10 window .addEventListener ('unhandledrejection' , (event ) => { console .error ('未处理的 Promise rejection:' , event.reason ); event.preventDefault (); }); window .addEventListener ('error' , (event ) => { console .error ('全局错误:' , event.error ); });
Node.js 环境 :
1 2 3 4 5 6 7 8 9 10 process.on ('unhandledRejection' , (reason, promise ) => { console .error ('未处理的 Promise rejection:' , reason); }); process.on ('uncaughtException' , (error ) => { console .error ('未捕获的异常:' , error); process.exit (1 ); });
2.6 柯里化与函数组合在异步中的应用 2.6.1 柯里化(Currying) 柯里化是将多参数函数转换为一系列单参数函数的技术。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function add (a, b ) { return a + b; } function addCurried (a ) { return function (b ) { return a + b; }; } const add5 = addCurried (5 );console .log (add5 (3 )); const addCurriedArrow = a => b => a + b;const add5Arrow = addCurriedArrow (5 );console .log (add5Arrow (3 ));
异步中的柯里化 :
1 2 3 4 5 6 7 8 9 10 11 const delay = ms => value => new Promise (resolve => setTimeout (() => resolve (value), ms)); const after1s = delay (1000 );const after2s = delay (2000 );after1s ('1秒后' ).then (console .log );after2s ('2秒后' ).then (console .log );
2.6.2 函数组合(Compose/Pipe) 函数组合是将多个函数组合成一个函数的技术。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const pipe = (...fns ) => (value ) => fns.reduce ((acc, fn ) => fn (acc), value); const compose = (...fns ) => (value ) => fns.reduceRight ((acc, fn ) => fn (acc), value); const add1 = x => x + 1 ;const multiply2 = x => x * 2 ;const square = x => x * x;const pipeline = pipe (add1, multiply2, square);console .log (pipeline (3 )); const composed = compose (square, multiply2, add1);console .log (composed (3 ));
异步函数组合 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const pipeAsync = (...fns ) => (value ) => fns.reduce ((promise, fn ) => promise.then (fn), Promise .resolve (value)); const fetchUser = async (id ) => { const res = await fetch (`/api/users/${id} ` ); return res.json (); }; const fetchProfile = async (user ) => { const res = await fetch (`/api/profiles/${user.id} ` ); return res.json (); }; const render = (profile ) => { console .log ('渲染:' , profile); }; const flow = pipeAsync (fetchUser, fetchProfile, render);flow (1 ).catch (console .error );
3. 高级异步模式 3.1 异步迭代器(Async Iterators) ES2018 引入了异步迭代器,用于处理异步数据流。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 async function * asyncGenerator ( ) { for (let i = 0 ; i < 3 ; i++) { await delay (100 ); yield i; } } (async () => { for await (const value of asyncGenerator ()) { console .log (value); } })();
3.2 异步队列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class AsyncQueue { constructor ( ) { this .queue = []; this .resolvers = []; } async enqueue (item ) { if (this .resolvers .length > 0 ) { const resolve = this .resolvers .shift (); resolve (item); } else { this .queue .push (item); } } async dequeue ( ) { if (this .queue .length > 0 ) { return this .queue .shift (); } return new Promise (resolve => { this .resolvers .push (resolve); }); } }
4. 实战应用场景 4.1 数据加载流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 async function loadUserDashboard (userId ) { try { const [user, settings] = await Promise .all ([ fetchUser (userId), fetchSettings (userId) ]); const posts = await fetchPosts (user.id ); const comments = await fetchComments (posts[0 ].id ); return { user, settings, posts, comments }; } catch (error) { console .error ('加载失败:' , error); throw error; } }
4.2 重试机制 1 2 3 4 5 6 7 8 9 10 11 12 13 async function retry (fn, retries = 3 , delay = 1000 ) { for (let i = 0 ; i < retries; i++) { try { return await fn (); } catch (error) { if (i === retries - 1 ) throw error; await new Promise (resolve => setTimeout (resolve, delay * (i + 1 ))); } } } const data = await retry (() => fetch ('/api/data' ).then (r => r.json ()));
4.3 并发控制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 async function concurrentLimit (tasks, limit ) { const results = []; const executing = []; for (const task of tasks) { const promise = task ().then (result => { executing.splice (executing.indexOf (promise), 1 ); return result; }); results.push (promise); executing.push (promise); if (executing.length >= limit) { await Promise .race (executing); } } return Promise .all (results); }
5. 代码片段速查 5.1 手写一个最小 Promise 封装 1 2 3 4 5 function delay (ms, value ) { return new Promise (resolve => setTimeout (() => resolve (value), ms)); } delay (200 , 'ok' ).then (console .log );
5.2 用 race 做超时保护 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function withTimeout (promise, ms ) { const timeout = new Promise ((_, reject ) => setTimeout (() => reject (new Error ('timeout' )), ms) ); return Promise .race ([promise, timeout]); } withTimeout (fetch ('/api/data' ), 5000 ) .then (response => response.json ()) .catch (error => { if (error.message === 'timeout' ) { console .error ('请求超时' ); } });
5.3 串行与并行对比 1 2 3 4 5 6 7 8 9 10 11 12 async function serial ( ) { const a = await taskA (); const b = await taskB (a); return b; } async function parallel ( ) { const [a, b] = await Promise .all ([taskA (), taskB ()]); return [a, b]; }
5.4 安全执行器(避免到处 try-catch) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const safe = fn => async (...args) => { try { return [null , await fn (...args)]; } catch (err) { return [err, null ]; } }; const getUserSafe = safe (getUser);const [err, user] = await getUserSafe (1 );if (err) { console .error ('错误:' , err); } else { console .log ('用户:' , user); }
5.5 防抖/节流 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function debounce (fn, wait = 200 ) { let timer = null ; return function (...args ) { clearTimeout (timer); timer = setTimeout (() => fn.apply (this , args), wait); }; } function throttle (fn, wait = 100 ) { let last = 0 ; return function (...args ) { const now = Date .now (); if (now - last >= wait) { last = now; fn.apply (this , args); } }; }
6. 常见问题与解决方案 6.1 await 在循环中 1 2 3 4 5 6 7 8 9 for (const id of userIds) { const user = await fetchUser (id); } const users = await Promise .all ( userIds.map (id => fetchUser (id)) );
6.2 Promise 链中的错误处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Promise .resolve () .then (() => { throw new Error ('错误' ); }) .then (() => { console .log ('这行不会执行' ); }); Promise .resolve () .then (() => { throw new Error ('错误' ); }) .catch (error => { console .error ('捕获错误:' , error); });
6.3 async 函数中的 return 1 2 3 4 5 6 7 8 9 10 async function getData ( ) { return fetch ('/api/data' ); } async function getData ( ) { const response = await fetch ('/api/data' ); return response.json (); }
7. 速记与总结 7.1 核心概念速记
“同步阻塞栈,异步排队等栈空;微任务优先于宏任务。”
同步代码在调用栈中执行,异步代码排队等待;微任务(Promise)优先于宏任务(setTimeout)。
“Promise 链返回值自动包;抛错/拒绝走最近 catch。”
Promise 链中返回普通值会自动包装成 Promise;错误会向下传播到最近的 catch。
“async 返回 Promise,await 只在 async 里用;并行用 all,超时用 race。”
async 函数返回 Promise;await 只能在 async 函数中使用;并行用 Promise.all,超时用 Promise.race。
“回调地狱不怕:拆函数、Promise 化、async 化、再加组合/管道。”
解决回调地狱的方法:拆分函数、使用 Promise、使用 async/await、使用函数组合。
“定时器不精确,递归 setTimeout 可控;后台标签页会被节流。”
setTimeout 不保证精确时间;使用递归 setTimeout 可以避免 setInterval 的问题;后台标签页会被浏览器节流。
7.2 选择指南
场景
推荐方案
原因
简单异步操作
Promise
简单直接
复杂异步流程
async/await
代码清晰
并行请求
Promise.all
性能好
超时控制
Promise.race
简单有效
部分成功处理
Promise.allSettled
不会因一个失败而整体失败
多服务器容错
Promise.any
一个成功即可
7.3 最佳实践
优先使用 async/await :代码更清晰易读
并行处理独立操作 :使用 Promise.all 提高性能
正确处理错误 :使用 try-catch 或 catch 方法
避免在循环中使用 await :使用 Promise.all 并行处理
使用防抖/节流 :优化频繁触发的事件
设置超时 :避免长时间等待
结语 :异步编程是 JavaScript 的核心特性之一。掌握 Promise、async/await 和各种异步模式,能够帮助你编写更高效、更易维护的代码。记住,理解事件循环是掌握异步编程的关键。