乐闻世界logo
搜索文章和话题

Promise

Promise 是一种用于延迟计算的策略,适用于多种并发风格:用于本地计算的线程和事件循环并发,以及同步和异步远程消息传递。Promise 代表一个异步操作的最终结果。使用 Promises 的主要方式是通过一个方法,该方法注册从 promise 的最终值或失败原因到新 promise 的转换。
Promise
查看更多相关内容
async/await 是如何工作的?与 Promise 有什么关系?async/await 是 ES2017 引入的语法糖,用于处理异步操作,它基于 Promise 构建,让异步代码看起来更像同步代码,大大提高了代码的可读性和可维护性。 ## async 函数 ### 基本概念 async 函数是使用 `async` 关键字声明的函数,它总是返回一个 Promise。即使函数内部没有显式返回 Promise,也会被包装成一个 Promise。 ### 基本用法 ```javascript async function fetchData() { return 'Hello World'; } // 等同于 function fetchData() { return Promise.resolve('Hello World'); } fetchData().then(result => console.log(result)); // 输出: Hello World ``` ### 返回 Promise ```javascript async function fetchData() { // 返回普通值 return 42; } async function fetchDataWithError() { // 抛出错误 throw new Error('出错了'); } fetchData().then(result => console.log(result)); // 输出: 42 fetchDataWithError().catch(error => console.error(error.message)); // 输出: 出错了 ``` ## await 表达式 ### 基本概念 await 关键字只能在 async 函数内部使用,它会暂停 async 函数的执行,等待 Promise 完成,然后返回 Promise 的结果。 ### 基本用法 ```javascript async function fetchData() { const promise = Promise.resolve('Hello'); const result = await promise; console.log(result); // 输出: Hello return result; } fetchData(); ``` ### 等待多个 Promise ```javascript async function fetchMultipleData() { const promise1 = fetch('/api/user'); const promise2 = fetch('/api/posts'); const [userResponse, postsResponse] = await Promise.all([promise1, promise2]); const user = await userResponse.json(); const posts = await postsResponse.json(); return { user, posts }; } ``` ## 错误处理 ### 使用 try/catch ```javascript async function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return data; } catch (error) { console.error('请求失败:', error.message); throw error; // 可以选择重新抛出错误 } } ``` ### 捕获特定错误 ```javascript async function fetchData() { try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { if (error.name === 'TypeError') { console.error('网络连接问题'); } else if (error.message.includes('HTTP error')) { console.error('服务器错误'); } else { console.error('未知错误:', error); } throw error; } } ``` ## async/await vs Promise.then() ### Promise.then() 链式调用 ```javascript function fetchData() { return fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => ({ user, posts })) .catch(error => { console.error(error); throw error; }); } ``` ### async/await(更易读) ```javascript async function fetchData() { try { const userResponse = await fetch('/api/user'); const user = await userResponse.json(); const postsResponse = await fetch(`/api/posts/${user.id}`); const posts = await postsResponse.json(); return { user, posts }; } catch (error) { console.error(error); throw error; } } ``` ## 并行执行 ### 使用 Promise.all ```javascript async function fetchAllData() { const [user, posts, comments] = await Promise.all([ fetch('/api/user').then(r => r.json()), fetch('/api/posts').then(r => r.json()), fetch('/api/comments').then(r => r.json()) ]); return { user, posts, comments }; } ``` ### 使用 Promise.allSettled ```javascript async function fetchAllDataWithErrors() { const results = await Promise.allSettled([ fetch('/api/user').then(r => r.json()), fetch('/api/posts').then(r => r.json()), fetch('/api/comments').then(r => r.json()) ]); results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`请求 ${index} 成功:`, result.value); } else { console.error(`请求 ${index} 失败:`, result.reason); } }); return results; } ``` ## 常见使用场景 ### 1. 顺序执行异步操作 ```javascript async function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; } ``` ### 2. 并行执行异步操作 ```javascript async function processItemsParallel(items) { const promises = items.map(item => processItem(item)); const results = await Promise.all(promises); return results; } ``` ### 3. 带超时的请求 ```javascript async function fetchWithTimeout(url, timeout = 5000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); return await response.json(); } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error('请求超时'); } throw error; } } ``` ### 4. 重试机制 ```javascript async function fetchWithRetry(url, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error(`尝试 ${i + 1} 失败:`, error.message); if (i === maxRetries - 1) { throw error; } await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } } ``` ## 最佳实践 ### 1. 总是使用 try/catch ```javascript // 不推荐:没有错误处理 async function fetchData() { const response = await fetch('/api/data'); return await response.json(); } // 推荐:添加错误处理 async function fetchData() { try { const response = await fetch('/api/data'); return await response.json(); } catch (error) { console.error('请求失败:', error); throw error; } } ``` ### 2. 避免在循环中顺序 await ```javascript // 不推荐:顺序执行,速度慢 async function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; } // 推荐:并行执行,速度快 async function processItems(items) { const promises = items.map(item => processItem(item)); return await Promise.all(promises); } ``` ### 3. 合理使用 Promise.all ```javascript // 推荐:并行执行独立的异步操作 async function fetchData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); return { user, posts, comments }; } ``` ### 4. 使用 finally 进行清理 ```javascript async function fetchData() { let connection; try { connection = await createConnection(); const data = await connection.query('SELECT * FROM users'); return data; } catch (error) { console.error('查询失败:', error); throw error; } finally { if (connection) { await connection.close(); } } } ``` ## 常见陷阱 ### 1. 忘记使用 await ```javascript // 错误:没有 await async function fetchData() { const promise = fetch('/api/data'); // promise 是一个 Promise 对象,不是数据 console.log(promise); // 输出: Promise {<pending>} } // 正确:使用 await async function fetchData() { const response = await fetch('/api/data'); const data = await response.json(); console.log(data); } ``` ### 2. 在非 async 函数中使用 await ```javascript // 错误:在非 async 函数中使用 await function fetchData() { const data = await fetch('/api/data'); // SyntaxError } // 正确:在 async 函数中使用 await async function fetchData() { const data = await fetch('/api/data'); return data; } ``` ### 3. 过度使用 try/catch ```javascript // 不推荐:过度使用 try/catch async function fetchData() { try { try { const response = await fetch('/api/data'); try { const data = await response.json(); return data; } catch (error) { console.error('解析失败:', error); } } catch (error) { console.error('请求失败:', error); } } catch (error) { console.error('未知错误:', error); } } // 推荐:合理使用 try/catch async function fetchData() { try { const response = await fetch('/api/data'); return await response.json(); } catch (error) { console.error('请求或解析失败:', error); throw error; } } ``` ## 与 Promise 的关系 async/await 本质上是 Promise 的语法糖,它们之间可以互相转换: ```javascript // async/await async function fetchData() { const response = await fetch('/api/data'); return await response.json(); } // 等同于 Promise function fetchData() { return fetch('/api/data') .then(response => response.json()); } ``` ## 总结 1. **async 函数总是返回 Promise**:即使返回普通值也会被包装成 Promise 2. **await 暂停执行**:等待 Promise 完成后继续执行 3. **使用 try/catch 处理错误**:确保错误被正确捕获和处理 4. **并行执行提高性能**:使用 Promise.all 并行执行独立的异步操作 5. **避免过度嵌套**:保持代码扁平和清晰 6. **理解与 Promise 的关系**:async/await 是 Promise 的语法糖,可以互相转换
前端 · 2月22日 14:31
什么是 Promise?Promise 有哪些状态?Promise 是 JavaScript 中处理异步操作的重要机制,它代表一个异步操作的最终完成或失败。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。一旦状态改变,就不会再变。 ## 核心概念 ### Promise 的三种状态 - **pending**: 初始状态,既不是成功也不是失败 - **fulfilled**: 操作成功完成 - **rejected**: 操作失败 ### 基本用法 ```javascript const promise = new Promise((resolve, reject) => { // 异步操作 setTimeout(() => { if (/* 操作成功 */) { resolve('成功的结果'); } else { reject('失败的原因'); } }, 1000); }); promise.then(result => { console.log(result); // 处理成功 }).catch(error => { console.log(error); // 处理失败 }); ``` ### Promise 链式调用 ```javascript fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log(posts)) .catch(error => console.error(error)); ``` ### Promise 静态方法 **Promise.all()**: 所有 Promise 都成功才返回成功,有一个失败就返回失败 ```javascript Promise.all([promise1, promise2, promise3]) .then(results => console.log(results)) .catch(error => console.error(error)); ``` **Promise.race()**: 返回最先完成的结果(无论成功或失败) ```javascript Promise.race([promise1, promise2]) .then(result => console.log(result)); ``` **Promise.allSettled()**: 等待所有 Promise 完成,返回每个 Promise 的状态 ```javascript Promise.allSettled([promise1, promise2]) .then(results => console.log(results)); ``` **Promise.any()**: 返回第一个成功的 Promise ```javascript Promise.any([promise1, promise2]) .then(result => console.log(result)); ``` ## 常见面试问题 ### 1. Promise 和回调函数的区别 - Promise 解决了回调地狱问题 - Promise 提供了更好的错误处理机制 - Promise 支持链式调用,代码更清晰 - Promise 状态不可逆,更符合直觉 ### 2. 如何处理 Promise 错误 使用 `.catch()` 方法捕获错误,或者在 `.then()` 的第二个参数中处理: ```javascript promise.then( result => console.log(result), error => console.error(error) ); // 或者 promise.then(result => console.log(result)) .catch(error => console.error(error)); ``` ### 3. Promise 的微任务机制 Promise 的回调属于微任务,会在当前宏任务执行完后立即执行,优先级高于宏任务(如 setTimeout)。 ### 4. async/await 与 Promise 的关系 async/await 是 Promise 的语法糖,让异步代码看起来像同步代码: ```javascript async function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return data; } catch (error) { console.error(error); } } ``` ## 最佳实践 1. **总是处理错误**: 使用 `.catch()` 或 try/catch 2. **避免嵌套 Promise**: 使用链式调用 3. **合理使用 Promise 静态方法**: 根据场景选择合适的方法 4. **理解事件循环**: 掌握微任务和宏任务的执行顺序 5. **性能优化**: 避免不必要的 Promise 包装
前端 · 2月22日 14:08
如何实现 Promise 的取消?Promise 的取消是一个常见但复杂的问题。Promise 本身不支持取消,但我们可以通过一些技巧来实现类似的功能。 ## 为什么 Promise 不能被取消? Promise 的设计遵循"不可逆"原则: - 一旦 Promise 状态改变(pending → fulfilled 或 pending → rejected),就不能再改变 - 这种设计保证了 Promise 的可靠性和可预测性 - 取消操作会引入额外的复杂性和不确定性 ## 实现取消的几种方法 ### 1. 使用 AbortController AbortController 是现代浏览器提供的取消异步操作的标准 API。 #### 基本用法 ```javascript const controller = new AbortController(); const signal = controller.signal; fetch('/api/data', { signal }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => { if (error.name === 'AbortError') { console.log('请求被取消'); } else { console.error('请求失败:', error); } }); // 取消请求 controller.abort(); ``` #### 封装可取消的 fetch ```javascript function fetchWithTimeout(url, options = {}, timeout = 5000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); return fetch(url, { ...options, signal: controller.signal }) .then(response => { clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .catch(error => { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error('请求超时'); } throw error; }); } // 使用示例 fetchWithTimeout('/api/data', {}, 3000) .then(data => console.log(data)) .catch(error => console.error(error)); ``` ### 2. 使用包装函数 通过包装函数来实现取消功能。 #### 基本实现 ```javascript function makeCancellable(promise) { let hasCancelled = false; const wrappedPromise = new Promise((resolve, reject) => { promise.then( value => { if (!hasCancelled) resolve(value); }, error => { if (!hasCancelled) reject(error); } ); }); return { promise: wrappedPromise, cancel: () => { hasCancelled = true; } }; } // 使用示例 const { promise, cancel } = makeCancellable( fetch('/api/data').then(r => r.json()) ); promise .then(data => console.log(data)) .catch(error => { if (error.name === 'CancellationError') { console.log('操作被取消'); } else { console.error('操作失败:', error); } }); // 取消操作 cancel(); ``` #### 完整实现 ```javascript class CancellablePromise { constructor(executor) { this.isCancelled = false; this.rejectors = []; this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; executor( value => { if (!this.isCancelled) { resolve(value); } }, error => { if (!this.isCancelled) { reject(error); } } ); }); } cancel() { this.isCancelled = true; this.rejectors.forEach(rejector => { rejector(new Error('Promise cancelled')); }); } then(onFulfilled, onRejected) { const promise = this.promise.then(onFulfilled, onRejected); return new CancellablePromise((resolve, reject) => { this.rejectors.push(reject); promise.then(resolve, reject); }); } catch(onRejected) { return this.then(null, onRejected); } finally(onFinally) { return this.then( value => { onFinally(); return value; }, error => { onFinally(); throw error; } ); } } // 使用示例 const cancellablePromise = new CancellablePromise((resolve) => { setTimeout(() => { resolve('完成'); }, 2000); }); cancellablePromise .then(result => console.log(result)) .catch(error => console.error(error)); // 取消 setTimeout(() => { cancellablePromise.cancel(); }, 1000); ``` ### 3. 使用令牌(Token)模式 通过传递令牌来检查是否应该继续执行。 ```javascript class CancellationToken { constructor() { this.isCancelled = false; } cancel() { this.isCancelled = true; } throwIfCancelled() { if (this.isCancelled) { throw new Error('Operation cancelled'); } } } function fetchWithToken(url, token) { return fetch(url) .then(response => { token.throwIfCancelled(); return response.json(); }) .then(data => { token.throwIfCancelled(); return data; }); } // 使用示例 const token = new CancellationToken(); fetchWithToken('/api/data', token) .then(data => console.log(data)) .catch(error => { if (error.message === 'Operation cancelled') { console.log('操作被取消'); } else { console.error('操作失败:', error); } }); // 取消操作 setTimeout(() => { token.cancel(); }, 1000); ``` ### 4. 使用 Promise.race 实现超时 ```javascript function promiseWithTimeout(promise, timeout = 5000) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new Error('Timeout')); }, timeout); }); return Promise.race([promise, timeoutPromise]); } // 使用示例 promiseWithTimeout( fetch('/api/data').then(r => r.json()), 3000 ) .then(data => console.log(data)) .catch(error => { if (error.message === 'Timeout') { console.log('请求超时'); } else { console.error('请求失败:', error); } }); ``` ## 实际应用场景 ### 1. 取消重复的搜索请求 ```javascript class SearchService { constructor() { this.currentController = null; } async search(query) { // 取消之前的请求 if (this.currentController) { this.currentController.abort(); } this.currentController = new AbortController(); try { const response = await fetch(`/api/search?q=${query}`, { signal: this.currentController.signal }); return await response.json(); } catch (error) { if (error.name === 'AbortError') { console.log('搜索请求被取消'); return null; } throw error; } } } // 使用示例 const searchService = new SearchService(); // 用户快速输入,只保留最后一次搜索 searchService.search('hello'); searchService.search('hello world'); searchService.search('hello world example'); ``` ### 2. 取消长时间运行的任务 ```javascript class TaskManager { constructor() { this.tasks = new Map(); } async runTask(taskId, task) { const controller = new AbortController(); this.tasks.set(taskId, controller); try { const result = await task(controller.signal); return result; } catch (error) { if (error.name === 'AbortError') { console.log(`任务 ${taskId} 被取消`); return null; } throw error; } finally { this.tasks.delete(taskId); } } cancelTask(taskId) { const controller = this.tasks.get(taskId); if (controller) { controller.abort(); } } } // 使用示例 const taskManager = new TaskManager(); // 运行任务 taskManager.runTask('task1', async (signal) => { for (let i = 0; i < 10; i++) { signal.throwIfAborted(); await new Promise(resolve => setTimeout(resolve, 1000)); console.log(`步骤 ${i + 1} 完成`); } return '任务完成'; }); // 取消任务 setTimeout(() => { taskManager.cancelTask('task1'); }, 3000); ``` ### 3. 组件卸载时取消请求 ```javascript class Component { constructor() { this.controller = new AbortController(); } async fetchData() { try { const response = await fetch('/api/data', { signal: this.controller.signal }); return await response.json(); } catch (error) { if (error.name === 'AbortError') { console.log('组件卸载,请求被取消'); return null; } throw error; } } destroy() { this.controller.abort(); } } // 使用示例 const component = new Component(); component.fetchData().then(data => { if (data) { console.log('数据加载成功:', data); } }); // 组件卸载时 setTimeout(() => { component.destroy(); }, 1000); ``` ## 最佳实践 ### 1. 总是清理资源 ```javascript async function fetchData() { const controller = new AbortController(); try { const response = await fetch('/api/data', { signal: controller.signal }); return await response.json(); } finally { controller.abort(); } } ``` ### 2. 提供取消回调 ```javascript function fetchWithCancel(url, onCancel) { const controller = new AbortController(); const promise = fetch(url, { signal: controller.signal }) .then(response => response.json()); promise.cancel = () => { controller.abort(); if (onCancel) { onCancel(); } }; return promise; } // 使用示例 const promise = fetchWithCancel('/api/data', () => { console.log('请求被取消'); }); promise.then(data => console.log(data)); // 取消请求 promise.cancel(); ``` ### 3. 处理取消错误 ```javascript async function fetchWithCancellation(url) { const controller = new AbortController(); try { const response = await fetch(url, { signal: controller.signal }); return await response.json(); } catch (error) { if (error.name === 'AbortError') { console.log('请求被取消'); return null; } throw error; } } ``` ## 总结 1. **Promise 本身不支持取消**:需要通过其他方式实现 2. **AbortController 是标准方案**:现代浏览器推荐使用 3. **包装函数提供灵活性**:可以根据需求定制取消逻辑 4. **令牌模式适合复杂场景**:可以精细控制取消时机 5. **总是清理资源**:避免内存泄漏 6. **处理取消错误**:区分取消错误和其他错误 7. **提供取消回调**:让调用者知道操作被取消 8. **考虑用户体验**:取消操作应该快速响应
前端 · 2月22日 14:07
Promise 的常见陷阱和最佳实践有哪些?Promise 的常见陷阱和最佳实践是每个 JavaScript 开发者都应该掌握的知识。了解这些陷阱可以帮助我们写出更健壮、更高效的异步代码。 ## 常见陷阱 ### 1. 忘记返回 Promise **问题示例:** ```javascript // 不推荐:忘记返回 Promise function fetchData() { fetch('/api/data') .then(response => response.json()) .then(data => { console.log(data); // 忘记返回数据 }); } // 调用者无法获取数据 fetchData().then(data => { console.log(data); // undefined }); ``` **正确做法:** ```javascript // 推荐:返回 Promise function fetchData() { return fetch('/api/data') .then(response => response.json()) .then(data => { console.log(data); return data; // 返回数据 }); } // 调用者可以获取数据 fetchData().then(data => { console.log(data); // 实际数据 }); ``` ### 2. 在 then 中嵌套 Promise **问题示例:** ```javascript // 不推荐:嵌套 Promise fetch('/api/user') .then(response => response.json()) .then(user => { fetch(`/api/posts/${user.id}`) .then(response => response.json()) .then(posts => { console.log(posts); }); }); ``` **正确做法:** ```javascript // 推荐:扁平链式调用 fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log(posts)); ``` ### 3. 忘记处理错误 **问题示例:** ```javascript // 不推荐:没有错误处理 fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)); // 如果请求失败,错误会被忽略 ``` **正确做法:** ```javascript // 推荐:添加错误处理 fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('请求失败:', error)); ``` ### 4. 在循环中顺序 await **问题示例:** ```javascript // 不推荐:顺序执行,速度慢 async function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; } ``` **正确做法:** ```javascript // 推荐:并行执行,速度快 async function processItems(items) { const promises = items.map(item => processItem(item)); return await Promise.all(promises); } ``` ### 5. 混用 async/await 和 Promise.then() **问题示例:** ```javascript // 不推荐:混用导致代码混乱 async function fetchData() { const response = await fetch('/api/data'); return response.json().then(data => { console.log(data); return data; }); } ``` **正确做法:** ```javascript // 推荐:统一使用 async/await async function fetchData() { const response = await fetch('/api/data'); const data = await response.json(); console.log(data); return data; } ``` ### 6. 不必要的 Promise 包装 **问题示例:** ```javascript // 不推荐:不必要的 Promise 包装 function fetchData() { return new Promise((resolve) => { fetch('/api/data') .then(response => response.json()) .then(data => resolve(data)); }); } ``` **正确做法:** ```javascript // 推荐:直接返回 Promise function fetchData() { return fetch('/api/data') .then(response => response.json()); } ``` ### 7. 在构造函数中执行异步操作 **问题示例:** ```javascript // 不推荐:在构造函数中执行异步操作 class User { constructor(id) { this.id = id; this.data = null; fetch(`/api/users/${id}`) .then(response => response.json()) .then(data => { this.data = data; }); } getData() { return this.data; // 可能返回 null } } ``` **正确做法:** ```javascript // 推荐:使用静态工厂方法或初始化方法 class User { constructor(id, data) { this.id = id; this.data = data; } static async create(id) { const response = await fetch(`/api/users/${id}`); const data = await response.json(); return new User(id, data); } getData() { return this.data; // 保证有数据 } } // 使用 const user = await User.create(1); console.log(user.getData()); ``` ### 8. 过度使用 Promise.all **问题示例:** ```javascript // 不推荐:对不相关的操作使用 Promise.all async function fetchData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); // 如果只需要用户数据,其他请求是浪费的 return user; } ``` **正确做法:** ```javascript // 推荐:只请求需要的数据 async function fetchData() { const user = await fetchUser(); return user; } // 或者:按需加载 async function fetchAllData() { const user = await fetchUser(); // 只在需要时加载其他数据 if (user.hasPosts) { const posts = await fetchPosts(); return { user, posts }; } return { user }; } ``` ## 最佳实践 ### 1. 总是返回 Promise ```javascript // 推荐:函数总是返回 Promise function fetchData() { return fetch('/api/data') .then(response => response.json()); } // 调用者可以链式调用 fetchData() .then(data => processData(data)) .then(result => console.log(result)); ``` ### 2. 使用 async/await 提高可读性 ```javascript // 推荐:使用 async/await async function fetchUserData() { try { const userResponse = await fetch('/api/user'); const user = await userResponse.json(); const postsResponse = await fetch(`/api/posts/${user.id}`); const posts = await postsResponse.json(); return { user, posts }; } catch (error) { console.error('获取数据失败:', error); throw error; } } ``` ### 3. 合理使用 Promise.all ```javascript // 推荐:对独立的异步操作使用 Promise.all async function fetchAllData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); return { user, posts, comments }; } ``` ### 4. 使用 Promise.allSettled 处理部分失败 ```javascript // 推荐:使用 Promise.allSettled async function fetchMultipleUrls(urls) { const results = await Promise.allSettled( urls.map(url => fetch(url)) ); const successful = results .filter(r => r.status === 'fulfilled') .map(r => r.value); const failed = results .filter(r => r.status === 'rejected') .map(r => r.reason); return { successful, failed }; } ``` ### 5. 使用 Promise.race 实现超时 ```javascript // 推荐:使用 Promise.race 实现超时 function fetchWithTimeout(url, timeout = 5000) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Timeout')), timeout); }); return Promise.race([ fetch(url), timeoutPromise ]); } ``` ### 6. 使用 Promise.any 获取第一个成功的结果 ```javascript // 推荐:使用 Promise.any async function fetchFromMultipleSources(sources) { try { const response = await Promise.any( sources.map(source => fetch(source.url)) ); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.error('所有数据源都失败了'); throw new Error('无法获取数据'); } throw error; } } ``` ### 7. 使用 finally 进行清理 ```javascript // 推荐:使用 finally async function fetchDataWithCleanup() { let connection; try { connection = await createConnection(); const data = await connection.query('SELECT * FROM users'); return data; } catch (error) { console.error('查询失败:', error); throw error; } finally { if (connection) { await connection.close(); } } } ``` ### 8. 避免在循环中创建 Promise ```问题示例:** ```javascript // 不推荐:在循环中创建 Promise function processItems(items) { const results = []; for (const item of items) { const promise = new Promise((resolve) => { setTimeout(() => { resolve(processItem(item)); }, 1000); }); results.push(promise); } return Promise.all(results); } ``` **正确做法:** ```javascript // 推荐:使用 map 创建 Promise function processItems(items) { const promises = items.map(item => new Promise((resolve) => { setTimeout(() => { resolve(processItem(item)); }, 1000); }) ); return Promise.all(promises); } ``` ### 9. 使用 AbortController 取消请求 ```javascript // 推荐:使用 AbortController class DataFetcher { constructor() { this.controller = new AbortController(); } async fetch(url) { try { const response = await fetch(url, { signal: this.controller.signal }); return await response.json(); } catch (error) { if (error.name === 'AbortError') { console.log('请求被取消'); return null; } throw error; } } cancel() { this.controller.abort(); } } ``` ### 10. 实现重试机制 ```javascript // 推荐:实现重试机制 async function fetchWithRetry(url, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error(`尝试 ${i + 1} 失败:`, error.message); if (i === maxRetries - 1) { throw error; } await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)) ); } } } ``` ## 性能优化 ### 1. 避免不必要的 await ```javascript // 不推荐:不必要的 await async function fetchData() { const data1 = await fetch('/api/data1'); const data2 = await fetch('/api/data2'); const data3 = await fetch('/api/data3'); return { data1, data2, data3 }; } // 推荐:并行执行 async function fetchData() { const [data1, data2, data3] = await Promise.all([ fetch('/api/data1'), fetch('/api/data2'), fetch('/api/data3') ]); return { data1, data2, data3 }; } ``` ### 2. 使用缓存 ```javascript // 推荐:使用缓存 const cache = new Map(); async function fetchWithCache(url) { if (cache.has(url)) { return cache.get(url); } const data = await fetch(url).then(r => r.json()); cache.set(url, data); return data; } ``` ### 3. 实现请求去重 ```javascript // 推荐:实现请求去重 const pendingRequests = new Map(); async function fetchDeduplicated(url) { if (pendingRequests.has(url)) { return pendingRequests.get(url); } const promise = fetch(url) .then(response => response.json()) .finally(() => { pendingRequests.delete(url); }); pendingRequests.set(url, promise); return promise; } ``` ## 总结 1. **总是返回 Promise**:让调用者可以链式调用 2. **使用 async/await**:提高代码可读性 3. **避免嵌套 Promise**:保持代码扁平 4. **处理错误**:总是添加错误处理 5. **合理使用 Promise.all**:对独立的异步操作使用 6. **使用 Promise.allSettled**:处理部分失败的场景 7. **使用 Promise.race**:实现超时和竞争 8. **使用 Promise.any**:获取第一个成功的结果 9. **使用 finally**:进行清理工作 10. **实现重试机制**:提高可靠性 11. **使用 AbortController**:取消请求 12. **避免不必要的 await**:并行执行独立的异步操作 13. **使用缓存**:减少重复请求 14. **实现请求去重**:避免同时发起相同的请求
前端 · 2月22日 14:07
如何实现 Promise 的并发控制?Promise 的并发控制是一个重要的性能优化技术,它允许我们限制同时执行的异步操作数量,避免资源耗尽或性能下降。 ## 为什么需要并发控制 ### 问题场景 ```javascript // 不推荐:同时发起大量请求 async function fetchAllUrls(urls) { const promises = urls.map(url => fetch(url)); const results = await Promise.all(promises); return results; } // 问题: // 1. 可能导致浏览器或服务器资源耗尽 // 2. 网络带宽可能被占满 // 3. 可能触发服务器的限流机制 // 4. 内存占用过高 ``` ## 基本并发控制实现 ### 1. 使用 p-limit 库 ```javascript import pLimit from 'p-limit'; async function fetchWithLimit(urls, concurrency = 5) { const limit = pLimit(concurrency); const promises = urls.map(url => limit(() => fetch(url)) ); const results = await Promise.all(promises); return results; } ``` ### 2. 手动实现并发控制 ```javascript async function asyncPool(poolLimit, array, iteratorFn) { const ret = []; const executing = []; for (const item of array) { const p = Promise.resolve().then(() => iteratorFn(item)); ret.push(p); if (poolLimit <= array.length) { const e = p.then(() => executing.splice(executing.indexOf(e), 1)); executing.push(e); if (executing.length >= poolLimit) { await Promise.race(executing); } } } return Promise.all(ret); } // 使用示例 async function fetchWithConcurrency(urls, concurrency = 5) { return asyncPool(concurrency, urls, url => fetch(url)); } ``` ### 3. 使用队列实现 ```javascript class ConcurrencyControl { constructor(concurrency) { this.concurrency = concurrency; this.queue = []; this.running = 0; } async run(task) { if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; try { return await task(); } finally { this.running--; const next = this.queue.shift(); if (next) next(); } } } // 使用示例 async function fetchWithControl(urls, concurrency = 5) { const control = new ConcurrencyControl(concurrency); const promises = urls.map(url => control.run(() => fetch(url)) ); return Promise.all(promises); } ``` ## 高级并发控制实现 ### 1. 带重试机制的并发控制 ```javascript class ConcurrencyControlWithRetry { constructor(concurrency, maxRetries = 3) { this.concurrency = concurrency; this.maxRetries = maxRetries; this.queue = []; this.running = 0; } async run(task) { if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; let lastError; for (let i = 0; i < this.maxRetries; i++) { try { const result = await task(); return result; } catch (error) { lastError = error; if (i < this.maxRetries - 1) { await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)) ); } } } throw lastError; } } // 使用示例 async function fetchWithRetry(urls, concurrency = 5) { const control = new ConcurrencyControlWithRetry(concurrency, 3); const promises = urls.map(url => control.run(() => fetch(url)) ); return Promise.all(promises); } ``` ### 2. 带超时的并发控制 ```javascript class ConcurrencyControlWithTimeout { constructor(concurrency, timeout = 5000) { this.concurrency = concurrency; this.timeout = timeout; this.queue = []; this.running = 0; } async run(task) { if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; try { const result = await Promise.race([ task(), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), this.timeout) ) ]); return result; } finally { this.running--; const next = this.queue.shift(); if (next) next(); } } } // 使用示例 async function fetchWithTimeout(urls, concurrency = 5) { const control = new ConcurrencyControlWithTimeout(concurrency, 5000); const promises = urls.map(url => control.run(() => fetch(url)) ); return Promise.all(promises); } ``` ### 3. 带优先级的并发控制 ```javascript class PriorityConcurrencyControl { constructor(concurrency) { this.concurrency = concurrency; this.queue = []; this.running = 0; } async run(task, priority = 0) { const taskWrapper = { task, priority, resolve: null }; this.queue.push(taskWrapper); this.queue.sort((a, b) => b.priority - a.priority); if (this.running >= this.concurrency) { await new Promise(resolve => { taskWrapper.resolve = resolve; }); } this.running++; try { return await task(); } finally { this.running--; const next = this.queue.find(t => t.resolve); if (next) { this.queue.splice(this.queue.indexOf(next), 1); next.resolve(); } } } } // 使用示例 async function fetchWithPriority(urls, concurrency = 5) { const control = new PriorityConcurrencyControl(concurrency); const promises = urls.map((url, index) => control.run(() => fetch(url), index % 3) ); return Promise.all(promises); } ``` ## 实际应用场景 ### 1. 批量下载文件 ```javascript async function downloadFiles(urls, concurrency = 3) { const control = new ConcurrencyControl(concurrency); const results = await Promise.all( urls.map(url => control.run(async () => { const response = await fetch(url); const blob = await response.blob(); return { url, blob }; }) ) ); return results; } ``` ### 2. 批量处理数据库查询 ```javascript async function processQueries(queries, concurrency = 5) { const control = new ConcurrencyControl(concurrency); const results = await Promise.all( queries.map(query => control.run(() => database.execute(query)) ) ); return results; } ``` ### 3. 批量发送邮件 ```javascript async function sendEmails(recipients, concurrency = 10) { const control = new ConcurrencyControl(concurrency); const results = await Promise.allSettled( recipients.map(recipient => control.run(() => emailService.send(recipient)) ) ); const successful = results.filter(r => r.status === 'fulfilled').length; const failed = results.filter(r => r.status === 'rejected').length; console.log(`发送完成: 成功 ${successful}, 失败 ${failed}`); return results; } ``` ## 性能优化技巧 ### 1. 动态调整并发数 ```javascript class AdaptiveConcurrencyControl { constructor(initialConcurrency = 5, maxConcurrency = 20) { this.concurrency = initialConcurrency; this.maxConcurrency = maxConcurrency; this.minConcurrency = 1; this.queue = []; this.running = 0; this.successCount = 0; this.errorCount = 0; } async run(task) { if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; try { const result = await task(); this.successCount++; this.adjustConcurrency(); return result; } catch (error) { this.errorCount++; this.adjustConcurrency(); throw error; } finally { this.running--; const next = this.queue.shift(); if (next) next(); } } adjustConcurrency() { const total = this.successCount + this.errorCount; const errorRate = this.errorCount / total; if (errorRate < 0.1 && this.concurrency < this.maxConcurrency) { this.concurrency = Math.min(this.concurrency + 1, this.maxConcurrency); } else if (errorRate > 0.3 && this.concurrency > this.minConcurrency) { this.concurrency = Math.max(this.concurrency - 1, this.minConcurrency); } } } ``` ### 2. 进度监控 ```javascript class ConcurrencyControlWithProgress { constructor(concurrency, onProgress) { this.concurrency = concurrency; this.onProgress = onProgress; this.queue = []; this.running = 0; this.completed = 0; this.total = 0; } async run(task) { this.total++; if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; try { const result = await task(); return result; } finally { this.running++; this.completed++; this.onProgress(this.completed, this.total); this.running--; const next = this.queue.shift(); if (next) next(); } } } // 使用示例 async function fetchWithProgress(urls, concurrency = 5) { const control = new ConcurrencyControlWithProgress(concurrency, (completed, total) => { console.log(`进度: ${completed}/${total} (${(completed/total*100).toFixed(1)}%)`); }); const promises = urls.map(url => control.run(() => fetch(url)) ); return Promise.all(promises); } ``` ## 常见问题 ### 1. 如何选择合适的并发数? ```javascript // 根据网络类型选择 function getOptimalConcurrency() { const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; if (connection) { return Math.min(connection.downlink || 4, 10); } return 4; // 默认值 } ``` ### 2. 如何处理失败的任务? ```javascript async function fetchWithPartialFailure(urls, concurrency = 5) { const control = new ConcurrencyControl(concurrency); const results = await Promise.allSettled( urls.map(url => control.run(() => fetch(url)) ) ); const successful = results.filter(r => r.status === 'fulfilled'); const failed = results.filter(r => r.status === 'rejected'); console.log(`成功: ${successful.length}, 失败: ${failed.length}`); return { successful, failed }; } ``` ## 总结 1. **避免资源耗尽**:限制同时执行的异步操作数量 2. **提高性能**:合理的并发控制可以提高整体性能 3. **灵活实现**:可以根据需求实现不同的并发控制策略 4. **错误处理**:结合重试、超时等机制提高可靠性 5. **进度监控**:实时监控任务执行进度 6. **动态调整**:根据实际情况动态调整并发数
前端 · 2月22日 14:07
如何优化 Promise 的性能?Promise 的性能优化是提升应用响应速度和用户体验的关键。通过合理使用 Promise 和相关技术,可以显著提高异步操作的效率。 ## 避免不必要的 Promise 包装 ### 问题示例 ```javascript // 不推荐:不必要的 Promise 包装 function fetchData() { return new Promise((resolve) => { resolve(fetch('/api/data')); }); } // 推荐:直接返回 Promise function fetchData() { return fetch('/api/data'); } ``` ### 优化原因 不必要的 Promise 包装会增加额外的开销,包括: - 创建新的 Promise 对象 - 额外的微任务调度 - 增加内存占用 ## 并行执行独立操作 ### 顺序执行(慢) ```javascript // 不推荐:顺序执行 async function fetchAllData() { const user = await fetchUser(); const posts = await fetchPosts(); const comments = await fetchComments(); return { user, posts, comments }; } ``` ### 并行执行(快) ```javascript // 推荐:并行执行 async function fetchAllData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); return { user, posts, comments }; } ``` ### 性能对比 假设每个请求需要 100ms: - 顺序执行:300ms - 并行执行:100ms(3倍提升) ## 避免过长的 Promise 链 ### 问题示例 ```javascript // 不推荐:过长的 Promise 链 function processLargeData(data) { return Promise.resolve(data) .then(data => processData1(data)) .then(data => processData2(data)) .then(data => processData3(data)) .then(data => processData4(data)) .then(data => processData5(data)) .then(data => processData6(data)) .then(data => processData7(data)) .then(data => processData8(data)); } ``` ### 优化方案 ```javascript // 推荐:使用 async/await async function processLargeData(data) { data = await processData1(data); data = await processData2(data); data = await processData3(data); data = await processData4(data); data = await processData5(data); data = await processData6(data); data = await processData7(data); data = await processData8(data); return data; } // 或者:使用函数组合 function processLargeData(data) { return [processData1, processData2, processData3, processData4, processData5, processData6, processData7, processData8] .reduce((promise, processor) => promise.then(processor), Promise.resolve(data) ); } ``` ## 合理使用缓存 ### 基本缓存实现 ```javascript const cache = new Map(); function fetchWithCache(url) { if (cache.has(url)) { return Promise.resolve(cache.get(url)); } return fetch(url) .then(response => response.json()) .then(data => { cache.set(url, data); return data; }); } ``` ### 带过期时间的缓存 ```javascript class PromiseCache { constructor(ttl = 60000) { this.cache = new Map(); this.ttl = ttl; } get(key) { const item = this.cache.get(key); if (!item) return null; if (Date.now() > item.expiry) { this.cache.delete(key); return null; } return item.value; } set(key, value) { this.cache.set(key, { value, expiry: Date.now() + this.ttl }); } async fetch(key, fetcher) { const cached = this.get(key); if (cached) return cached; const value = await fetcher(); this.set(key, value); return value; } } // 使用示例 const cache = new PromiseCache(60000); async function fetchUser(id) { return cache.fetch(`user:${id}`, () => fetch(`/api/users/${id}`).then(r => r.json()) ); } ``` ## 请求去重 ### 基本去重实现 ```javascript const pendingRequests = new Map(); function fetchDeduplicated(url) { if (pendingRequests.has(url)) { return pendingRequests.get(url); } const promise = fetch(url) .then(response => response.json()) .finally(() => { pendingRequests.delete(url); }); pendingRequests.set(url, promise); return promise; } ``` ### 完整的去重实现 ```javascript class RequestDeduplicator { constructor() { this.pendingRequests = new Map(); } async fetch(url, options = {}) { const key = this.getRequestKey(url, options); if (this.pendingRequests.has(key)) { return this.pendingRequests.get(key); } const promise = fetch(url, options) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .finally(() => { this.pendingRequests.delete(key); }); this.pendingRequests.set(key, promise); return promise; } getRequestKey(url, options) { return JSON.stringify({ url, options }); } } // 使用示例 const deduplicator = new RequestDeduplicator(); // 多次调用同一个 URL,只会发起一次请求 Promise.all([ deduplicator.fetch('/api/user'), deduplicator.fetch('/api/user'), deduplicator.fetch('/api/user') ]).then(results => { console.log('所有请求返回相同结果:', results); }); ``` ## 批量处理 ### 问题示例 ```javascript // 不推荐:逐个处理 async function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; } ``` ### 优化方案 ```javascript // 推荐:批量处理 async function processItems(items, batchSize = 10) { const results = []; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); const batchResults = await Promise.all( batch.map(item => processItem(item)) ); results.push(...batchResults); } return results; } ``` ## 错误处理优化 ### 避免过度使用 try/catch ```javascript // 不推荐:过度使用 try/catch async function fetchData() { try { try { const response = await fetch('/api/data'); try { const data = await response.json(); try { const processed = await processData(data); return processed; } catch (error) { console.error('处理失败:', error); } } catch (error) { console.error('解析失败:', error); } } catch (error) { console.error('请求失败:', error); } } catch (error) { console.error('未知错误:', error); } } ``` ### 优化方案 ```javascript // 推荐:合理的错误处理 async function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return await processData(data); } catch (error) { if (error instanceof NetworkError) { console.error('网络错误:', error.message); } else if (error instanceof ParseError) { console.error('解析错误:', error.message); } else { console.error('未知错误:', error); } throw error; } } ``` ## 内存优化 ### 避免内存泄漏 ```javascript // 不推荐:可能导致内存泄漏 class DataFetcher { constructor() { this.cache = new Map(); } async fetch(url) { if (this.cache.has(url)) { return this.cache.get(url); } const data = await fetch(url).then(r => r.json()); this.cache.set(url, data); return data; } } ``` ### 优化方案 ```javascript // 推荐:使用 WeakMap 或限制缓存大小 class DataFetcher { constructor(maxSize = 100) { this.cache = new Map(); this.maxSize = maxSize; } async fetch(url) { if (this.cache.has(url)) { return this.cache.get(url); } const data = await fetch(url).then(r => r.json()); // 限制缓存大小 if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(url, data); return data; } } ``` ## 性能监控 ### 监控 Promise 执行时间 ```javascript function withPerformanceTracking(promise, label) { const startTime = performance.now(); return promise .then(result => { const duration = performance.now() - startTime; console.log(`${label} 完成,耗时: ${duration.toFixed(2)}ms`); return result; }) .catch(error => { const duration = performance.now() - startTime; console.error(`${label} 失败,耗时: ${duration.toFixed(2)}ms`, error); throw error; }); } // 使用示例 async function fetchData() { return withPerformanceTracking( fetch('/api/data').then(r => r.json()), 'fetchData' ); } ``` ### 监控并发请求数 ```javascript class RequestMonitor { constructor() { this.activeRequests = 0; this.maxConcurrentRequests = 0; } async monitor(promise) { this.activeRequests++; this.maxConcurrentRequests = Math.max( this.activeRequests, this.maxConcurrentRequests ); try { return await promise; } finally { this.activeRequests--; } } getStats() { return { activeRequests: this.activeRequests, maxConcurrentRequests: this.maxConcurrentRequests }; } } // 使用示例 const monitor = new RequestMonitor(); async function fetchWithMonitor(url) { return monitor.monitor(fetch(url)); } ``` ## 总结 1. **避免不必要的 Promise 包装**:减少额外的开销 2. **并行执行独立操作**:利用 Promise.all 提高性能 3. **避免过长的 Promise 链**:使用 async/await 提高可读性 4. **合理使用缓存**:减少重复请求 5. **实现请求去重**:避免重复的网络请求 6. **批量处理数据**:提高处理效率 7. **优化错误处理**:避免过度嵌套的 try/catch 8. **注意内存管理**:避免内存泄漏 9. **监控性能指标**:及时发现性能问题 10. **选择合适的并发数**:根据实际情况调整并发策略
前端 · 2月22日 14:07
如何理解 Promise 的链式调用?Promise 的链式调用是 Promise 最强大的特性之一,它允许我们以优雅的方式处理多个异步操作,避免了回调地狱的问题。 ## 基本概念 Promise 链式调用是指通过 `.then()` 方法返回一个新的 Promise,从而可以连续调用多个 `.then()` 方法。每个 `.then()` 方法接收前一个 Promise 的结果作为参数,并返回一个新的 Promise。 ## 链式调用的工作原理 ### 核心机制 1. `.then()` 方法总是返回一个新的 Promise 2. 前一个 `.then()` 的返回值会传递给下一个 `.then()` 3. 如果返回的是 Promise,会等待其完成后再传递结果 4. 错误会沿着链向下传递,直到被 `.catch()` 捕获 ### 示例代码 ```javascript fetch('/api/user') .then(response => response.json()) .then(user => { console.log('用户信息:', user); return fetch(`/api/posts/${user.id}`); }) .then(response => response.json()) .then(posts => { console.log('用户文章:', posts); return posts; }) .catch(error => { console.error('发生错误:', error); }); ``` ## 链式调用的返回值处理 ### 1. 返回普通值 ```javascript Promise.resolve(1) .then(value => value + 1) .then(value => value * 2) .then(value => console.log(value)); // 输出: 4 ``` ### 2. 返回 Promise ```javascript Promise.resolve(1) .then(value => { return Promise.resolve(value + 1); }) .then(value => { return new Promise(resolve => { setTimeout(() => resolve(value * 2), 1000); }); }) .then(value => console.log(value)); // 输出: 4 ``` ### 3. 不返回值(返回 undefined) ```javascript Promise.resolve(1) .then(value => { console.log(value); // 输出: 1 // 不返回任何值,相当于返回 undefined }) .then(value => console.log(value)); // 输出: undefined ``` ### 4. 抛出错误 ```javascript Promise.resolve(1) .then(value => { throw new Error('出错了'); }) .catch(error => { console.error(error.message); // 输出: 出错了 return '恢复后的值'; }) .then(value => console.log(value)); // 输出: 恢复后的值 ``` ## 错误处理机制 ### 错误传递 ```javascript Promise.resolve() .then(() => { throw new Error('第一个错误'); }) .then(() => { console.log('这行不会执行'); }) .catch(error => { console.error('捕获错误:', error.message); return '继续执行'; }) .then(value => { console.log(value); // 输出: 继续执行 }); ``` ### 多个 catch ```javascript Promise.resolve() .then(() => { throw new Error('错误'); }) .catch(error => { console.error('第一个 catch:', error.message); throw new Error('新错误'); }) .catch(error => { console.error('第二个 catch:', error.message); }); ``` ## Promise 链式调用 vs 回调地狱 ### 回调地狱(不推荐) ```javascript fetch('/api/user', (error, response) => { if (error) { console.error(error); return; } response.json((error, user) => { if (error) { console.error(error); return; } fetch(`/api/posts/${user.id}`, (error, response) => { if (error) { console.error(error); return; } response.json((error, posts) => { if (error) { console.error(error); return; } console.log(posts); }); }); }); }); ``` ### Promise 链式调用(推荐) ```javascript fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log(posts)) .catch(error => console.error(error)); ``` ## 最佳实践 ### 1. 保持链的扁平 ```javascript // 不推荐:嵌套 Promise Promise.resolve() .then(() => { return Promise.resolve().then(() => { return Promise.resolve().then(() => { console.log('嵌套太深'); }); }); }); // 推荐:扁平链式 Promise.resolve() .then(() => {}) .then(() => {}) .then(() => console.log('扁平清晰')); ``` ### 2. 合理使用 finally ```javascript Promise.resolve() .then(() => console.log('执行操作')) .catch(error => console.error(error)) .finally(() => console.log('清理资源')); ``` ### 3. 错误处理要全面 ```javascript Promise.resolve() .then(data => { // 处理数据 return processData(data); }) .catch(error => { // 处理错误 console.error('处理失败:', error); // 可以返回默认值或重新抛出错误 return defaultValue; }); ``` ### 4. 避免在 then 中创建不必要的 Promise ```javascript // 不推荐 Promise.resolve(1) .then(value => { return new Promise(resolve => { resolve(value + 1); }); }); // 推荐 Promise.resolve(1) .then(value => value + 1); ``` ## 常见问题 ### 1. 如何在链中传递多个值? ```javascript Promise.all([promise1, promise2]) .then(([result1, result2]) => { console.log(result1, result2); }); ``` ### 2. 如何在链中跳过某些步骤? ```javascript Promise.resolve() .then(() => { if (shouldSkip) { return Promise.reject('skip'); } return doSomething(); }) .catch(error => { if (error === 'skip') { return '跳过的结果'; } throw error; }) .then(result => console.log(result)); ``` ### 3. 如何在链中添加日志? ```javascript Promise.resolve(1) .tap(value => console.log('当前值:', value)) .then(value => value + 1) .tap(value => console.log('新值:', value)); ``` ## 与 async/await 的对比 ### Promise 链式调用 ```javascript fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log(posts)) .catch(error => console.error(error)); ``` ### async/await(更易读) ```javascript async function fetchPosts() { try { const userResponse = await fetch('/api/user'); const user = await userResponse.json(); const postsResponse = await fetch(`/api/posts/${user.id}`); const posts = await postsResponse.json(); console.log(posts); } catch (error) { console.error(error); } } ``` async/await 本质上是 Promise 链式调用的语法糖,让异步代码看起来更像同步代码,提高了代码的可读性。
前端 · 2月22日 14:07
如何处理 Promise 的错误?Promise 的错误处理是使用 Promise 时必须掌握的重要技能。正确的错误处理可以确保程序的健壮性,避免未捕获的错误导致程序崩溃。 ## 错误处理的基本方法 ### 1. 使用 .catch() 方法 `.catch()` 是 Promise 错误处理的主要方法,它会捕获链中任何地方抛出的错误: ```javascript Promise.resolve() .then(() => { throw new Error('出错了'); }) .catch(error => { console.error('捕获到错误:', error.message); }); ``` ### 2. 在 .then() 的第二个参数中处理 `.then()` 方法可以接收两个参数:成功回调和失败回调: ```javascript Promise.resolve() .then( result => console.log('成功:', result), error => console.error('失败:', error.message) ); ``` **注意**:这种方式的错误处理只捕获前一个 Promise 的错误,不会捕获链中后续的错误。 ## 错误传播机制 ### 错误会沿着 Promise 链向下传播 ```javascript Promise.resolve() .then(() => { throw new Error('第一个错误'); }) .then(() => { console.log('这行不会执行'); }) .then(() => { console.log('这行也不会执行'); }) .catch(error => { console.error('最终捕获:', error.message); // 输出: 最终捕获: 第一个错误 }); ``` ### 错误可以被捕获后恢复 ```javascript Promise.resolve() .then(() => { throw new Error('出错了'); }) .catch(error => { console.error('捕获错误:', error.message); return '恢复后的值'; // 返回一个值,链可以继续 }) .then(value => { console.log('继续执行:', value); // 输出: 继续执行: 恢复后的值 }); ``` ## 常见错误处理场景 ### 1. 网络请求错误处理 ```javascript fetch('/api/data') .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { console.log('数据:', data); }) .catch(error => { console.error('请求失败:', error.message); // 可以在这里显示错误提示给用户 showErrorToUser('数据加载失败,请稍后重试'); }); ``` ### 2. 多个 Promise 的错误处理 ```javascript Promise.all([promise1, promise2, promise3]) .then(results => { console.log('全部成功:', results); }) .catch(error => { console.error('至少一个失败:', error.message); }); ``` ### 3. 使用 Promise.allSettled 处理部分失败 ```javascript Promise.allSettled([promise1, promise2, promise3]) .then(results => { results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`Promise ${index} 成功:`, result.value); } else { console.error(`Promise ${index} 失败:`, result.reason); } }); }); ``` ## 错误处理的最佳实践 ### 1. 总是添加错误处理 ```javascript // 不推荐:没有错误处理 fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)); // 推荐:添加错误处理 fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error)); ``` ### 2. 使用 finally 进行清理 ```javascript let isLoading = true; fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error)) .finally(() => { isLoading = false; console.log('请求完成,无论成功或失败'); }); ``` ### 3. 错误处理要具体 ```javascript // 不推荐:笼统的错误处理 Promise.resolve() .catch(error => { console.error('出错了'); }); // 推荐:具体的错误处理 Promise.resolve() .catch(error => { if (error instanceof NetworkError) { console.error('网络错误:', error.message); } else if (error instanceof ValidationError) { console.error('验证错误:', error.message); } else { console.error('未知错误:', error.message); } }); ``` ### 4. 考虑错误恢复策略 ```javascript async function fetchDataWithRetry(url, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error(`尝试 ${i + 1} 失败:`, error.message); if (i === maxRetries - 1) { throw error; // 最后一次尝试失败,抛出错误 } // 等待一段时间后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } } ``` ## async/await 中的错误处理 ### 使用 try/catch ```javascript async function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); console.log('数据:', data); } catch (error) { console.error('错误:', error.message); // 错误处理逻辑 } } ``` ### 捕获特定错误 ```javascript async function fetchData() { try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { if (error.name === 'TypeError') { console.error('网络连接问题'); } else if (error.message.includes('HTTP error')) { console.error('服务器错误'); } else { console.error('未知错误:', error); } throw error; // 可以选择重新抛出错误 } } ``` ## 未捕获的 Promise 错误 ### 全局错误处理 ```javascript // 处理未捕获的 Promise 错误 window.addEventListener('unhandledrejection', event => { console.error('未捕获的 Promise 错误:', event.reason); // 可以在这里记录错误或显示错误提示 event.preventDefault(); // 阻止默认的错误输出 }); // Node.js 环境中 process.on('unhandledRejection', (reason, promise) => { console.error('未捕获的 Promise 错误:', reason); }); ``` ## 常见错误处理陷阱 ### 1. 忘记在链中添加 catch ```javascript // 危险:没有错误处理 Promise.reject('出错了') .then(result => console.log(result)); // 错误会冒泡到全局,可能导致程序崩溃 // 安全:添加错误处理 Promise.reject('出错了') .then(result => console.log(result)) .catch(error => console.error(error)); ``` ### 2. 在 catch 中忘记重新抛出错误 ```javascript // 可能导致问题:错误被吞掉 Promise.reject('出错了') .catch(error => { console.error('捕获错误:', error); // 忘记重新抛出,后续代码会继续执行 }) .then(() => { console.log('这行会执行,即使前面有错误'); }); // 推荐:根据情况决定是否重新抛出 Promise.reject('出错了') .catch(error => { console.error('捕获错误:', error); // 如果错误无法恢复,重新抛出 throw error; }); ``` ### 3. 混用 then 的第二个参数和 catch ```javascript // 不推荐:容易混淆 Promise.resolve() .then( result => console.log('成功'), error => console.error('失败1') ) .catch(error => console.error('失败2')); // 推荐:统一使用 catch Promise.resolve() .then(result => console.log('成功')) .catch(error => console.error('失败')); ``` ## 总结 1. **总是添加错误处理**:使用 `.catch()` 或 `try/catch` 2. **理解错误传播**:错误会沿着 Promise 链向下传播 3. **使用 finally 清理**:无论成功或失败都要执行的代码放在 `finally` 中 4. **具体化错误处理**:根据错误类型进行不同的处理 5. **考虑错误恢复**:实现重试机制或降级策略 6. **避免错误吞掉**:确保错误被正确处理或重新抛出 7. **全局错误监控**:使用全局错误处理器捕获未处理的错误
前端 · 2月22日 14:07
Promise.all() 和 Promise.race() 的区别是什么?Promise.all() 和 Promise.race() 是 Promise 提供的两个重要的静态方法,它们用于处理多个 Promise 的并行执行,但行为和用途完全不同。 ## Promise.all() ### 基本概念 Promise.all() 接收一个 Promise 数组作为参数,返回一个新的 Promise。这个新的 Promise 会在所有输入的 Promise 都成功完成时才成功,返回的结果是所有 Promise 结果组成的数组(顺序与输入顺序一致)。如果任何一个 Promise 失败,Promise.all() 会立即失败,返回第一个失败的 Promise 的错误。 ### 使用场景 - 需要同时发起多个独立的请求,并等待所有请求都完成 - 多个异步操作之间存在依赖关系,需要所有结果都准备好后才能继续 ### 示例代码 ```javascript const promise1 = fetch('/api/user'); const promise2 = fetch('/api/posts'); const promise3 = fetch('/api/comments'); Promise.all([promise1, promise2, promise3]) .then(responses => { // 所有请求都成功 return Promise.all(responses.map(r => r.json())); }) .then(data => { console.log('所有数据:', data); }) .catch(error => { console.error('某个请求失败:', error); }); ``` ### 特点 1. **并行执行**: 所有 Promise 同时开始执行 2. **顺序保证**: 结果数组顺序与输入顺序一致 3. **快速失败**: 任何一个失败,整体立即失败 4. **空数组**: 如果传入空数组,立即返回成功 ## Promise.race() ### 基本概念 Promise.race() 同样接收一个 Promise 数组,返回一个新的 Promise。这个新的 Promise 会在第一个 Promise 完成(无论成功或失败)时立即完成,并返回第一个完成的 Promise 的结果或错误。 ### 使用场景 - 设置超时机制 - 从多个数据源获取数据,使用最快返回的结果 - 竞争条件处理 ### 示例代码 ```javascript // 超时示例 const fetchData = fetch('/api/data'); const timeout = new Promise((_, reject) => { setTimeout(() => reject(new Error('请求超时')), 5000); }); Promise.race([fetchData, timeout]) .then(response => console.log('数据获取成功')) .catch(error => console.error(error)); // 多数据源竞争示例 const source1 = fetch('https://api1.example.com/data'); const source2 = fetch('https://api2.example.com/data'); const source3 = fetch('https://api3.example.com/data'); Promise.race([source1, source2, source3]) .then(response => response.json()) .then(data => console.log('最快的数据源:', data)); ``` ### 特点 1. **快速返回**: 返回第一个完成的结果 2. **状态继承**: 成功或失败取决于第一个完成的 Promise 3. **不确定性**: 哪个 Promise 先完成是不确定的 4. **空数组**: 如果传入空数组,Promise 永远处于 pending 状态 ## 对比总结 | 特性 | Promise.all() | Promise.race() | |------|---------------|----------------| | 完成条件 | 所有 Promise 都成功 | 第一个 Promise 完成 | | 失败条件 | 任何一个 Promise 失败 | 第一个 Promise 失败 | | 返回结果 | 所有结果的数组 | 第一个完成的结果 | | 使用场景 | 需要所有结果 | 需要最快结果 | | 执行方式 | 并行执行 | 并行执行 | ## 其他相关方法 ### Promise.allSettled() ES2020 新增,等待所有 Promise 完成(无论成功或失败),返回每个 Promise 的状态和结果: ```javascript Promise.allSettled([promise1, promise2, promise3]) .then(results => { results.forEach(result => { if (result.status === 'fulfilled') { console.log('成功:', result.value); } else { console.log('失败:', result.reason); } }); }); ``` ### Promise.any() ES2021 新增,返回第一个成功的 Promise,如果所有 Promise 都失败,返回 AggregateError: ```javascript Promise.any([promise1, promise2, promise3]) .then(result => console.log('第一个成功:', result)) .catch(error => console.log('全部失败:', error)); ``` ## 实际应用建议 1. **使用 Promise.all()**: 当需要所有数据都准备好时,如加载多个必需的资源 2. **使用 Promise.race()**: 当只需要最快的结果时,如设置超时或从多个镜像源获取数据 3. **使用 Promise.allSettled()**: 当需要知道每个操作的结果,即使某些失败 4. **使用 Promise.any()**: 当只需要一个成功的结果,如从多个备份服务器获取数据
前端 · 2月22日 14:07
Promise.any() 的作用是什么?Promise.any() 是 ES2021 引入的 Promise 静态方法,它接收一个 Promise 数组,返回第一个成功完成的 Promise 的结果。如果所有 Promise 都失败,则返回 AggregateError。 ## 基本概念 Promise.any() 接收一个可迭代的 Promise 对象作为参数,返回一个新的 Promise。这个新的 Promise 会在第一个 Promise 成功完成时立即完成,返回该 Promise 的结果。如果所有 Promise 都失败,则返回一个 AggregateError,包含所有失败的原因。 ## 基本用法 ```javascript const promise1 = Promise.reject('错误1'); const promise2 = Promise.reject('错误2'); const promise3 = Promise.resolve('成功'); Promise.any([promise1, promise2, promise3]) .then(result => { console.log(result); // 输出: 成功 }) .catch(error => { console.error(error); }); ``` ## 与其他方法的对比 ### Promise.any() vs Promise.race() **Promise.any()**: 返回第一个成功的 Promise ```javascript const promise1 = Promise.reject('错误1'); const promise2 = Promise.resolve('成功'); const promise3 = Promise.resolve('另一个成功'); Promise.any([promise1, promise2, promise3]) .then(result => console.log(result)); // 输出: 成功 ``` **Promise.race()**: 返回第一个完成的 Promise(无论成功或失败) ```javascript const promise1 = Promise.reject('错误1'); const promise2 = Promise.resolve('成功'); const promise3 = Promise.resolve('另一个成功'); Promise.race([promise1, promise2, promise3]) .then(result => console.log(result)) .catch(error => console.error(error)); // 输出: 错误1 ``` ### Promise.any() vs Promise.all() **Promise.any()**: 只要有一个成功就返回 ```javascript const promise1 = Promise.reject('错误1'); const promise2 = Promise.resolve('成功'); const promise3 = Promise.reject('错误3'); Promise.any([promise1, promise2, promise3]) .then(result => console.log(result)); // 输出: 成功 ``` **Promise.all()**: 必须全部成功才返回 ```javascript const promise1 = Promise.reject('错误1'); const promise2 = Promise.resolve('成功'); const promise3 = Promise.reject('错误3'); Promise.all([promise1, promise2, promise3]) .then(result => console.log(result)) .catch(error => console.error(error)); // 输出: 错误1 ``` ### Promise.any() vs Promise.allSettled() **Promise.any()**: 返回第一个成功的结果 ```javascript const promise1 = Promise.reject('错误1'); const promise2 = Promise.resolve('成功'); const promise3 = Promise.reject('错误3'); Promise.any([promise1, promise2, promise3]) .then(result => console.log(result)); // 输出: 成功 ``` **Promise.allSettled()**: 返回所有 Promise 的状态 ```javascript const promise1 = Promise.reject('错误1'); const promise2 = Promise.resolve('成功'); const promise3 = Promise.reject('错误3'); Promise.allSettled([promise1, promise2, promise3]) .then(results => console.log(results)); // 输出: // [ // { status: 'rejected', reason: '错误1' }, // { status: 'fulfilled', value: '成功' }, // { status: 'rejected', reason: '错误3' } // ] ``` ## AggregateError 当所有 Promise 都失败时,Promise.any() 会返回一个 AggregateError,包含所有失败的原因。 ### 基本用法 ```javascript const promise1 = Promise.reject('错误1'); const promise2 = Promise.reject('错误2'); const promise3 = Promise.reject('错误3'); Promise.any([promise1, promise2, promise3]) .catch(error => { console.error(error instanceof AggregateError); // true console.error(error.message); // All promises were rejected console.error(error.errors); // ['错误1', '错误2', '错误3'] }); ``` ### 处理 AggregateError ```javascript async function fetchFromMultipleSources(urls) { try { const response = await Promise.any( urls.map(url => fetch(url)) ); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.error('所有数据源都失败了:'); error.errors.forEach((err, index) => { console.error(` ${urls[index]}: ${err.message}`); }); throw new Error('无法从任何数据源获取数据'); } throw error; } } ``` ## 实际应用场景 ### 1. 从多个数据源获取数据,使用最快成功的 ```javascript async function fetchFromFastestSource(sources) { try { const response = await Promise.any( sources.map(source => fetch(source.url)) ); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.error('所有数据源都失败了'); throw new Error('无法获取数据'); } throw error; } } // 使用示例 const sources = [ { name: '主服务器', url: 'https://api1.example.com/data' }, { name: '备用服务器1', url: 'https://api2.example.com/data' }, { name: '备用服务器2', url: 'https://api3.example.com/data' } ]; fetchFromFastestSource(sources) .then(data => console.log('数据:', data)) .catch(error => console.error(error)); ``` ### 2. 实现超时机制 ```javascript function fetchWithTimeout(url, timeout = 5000) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Timeout')), timeout); }); return Promise.any([ fetch(url).then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }), timeoutPromise ]); } // 使用示例 fetchWithTimeout('/api/data', 3000) .then(data => console.log('数据:', data)) .catch(error => { if (error.message === 'Timeout') { console.error('请求超时'); } else { console.error('请求失败:', error); } }); ``` ### 3. 尝试多个备份方案 ```javascript async function tryMultipleStrategies(strategies) { try { return await Promise.any( strategies.map(strategy => strategy()) ); } catch (error) { if (error instanceof AggregateError) { console.error('所有策略都失败了'); throw new Error('无法完成任务'); } throw error; } } // 使用示例 const strategies = [ () => fetch('/api/v1/data').then(r => r.json()), () => fetch('/api/v2/data').then(r => r.json()), () => fetch('/api/v3/data').then(r => r.json()) ]; tryMultipleStrategies(strategies) .then(data => console.log('数据:', data)) .catch(error => console.error(error)); ``` ### 4. 图片加载,使用第一个成功加载的 ```javascript async function loadFirstSuccessfulImage(imageUrls) { try { const imagePromises = imageUrls.map(url => { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = () => reject(new Error(`Failed to load: ${url}`)); img.src = url; }); }); return await Promise.any(imagePromises); } catch (error) { if (error instanceof AggregateError) { console.error('所有图片都加载失败'); throw new Error('无法加载图片'); } throw error; } } // 使用示例 const imageUrls = [ 'https://cdn1.example.com/image.jpg', 'https://cdn2.example.com/image.jpg', 'https://cdn3.example.com/image.jpg' ]; loadFirstSuccessfulImage(imageUrls) .then(img => document.body.appendChild(img)) .catch(error => console.error(error)); ``` ## 错误处理 ### 处理所有失败的情况 ```javascript async function fetchWithFallback(urls) { try { const response = await Promise.any( urls.map(url => fetch(url)) ); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.error('所有 URL 都失败了'); // 可以返回默认值或重新抛出错误 return { error: 'All sources failed' }; } throw error; } } ``` ### 记录所有失败的原因 ```javascript async function fetchWithLogging(urls) { try { const response = await Promise.any( urls.map(url => fetch(url)) ); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.error('所有 URL 都失败了:'); error.errors.forEach((err, index) => { console.error(` ${urls[index]}: ${err.message}`); }); throw new Error('无法获取数据'); } throw error; } } ``` ## 性能考虑 ### 空数组处理 ```javascript Promise.any([]) .then(result => console.log(result)) .catch(error => { console.error(error instanceof AggregateError); // true console.error(error.errors); // [] }); ``` ### 非 Promise 值处理 ```javascript Promise.any([1, 2, Promise.resolve(3)]) .then(result => console.log(result)); // 输出: 1 ``` ## 浏览器兼容性 Promise.any() 是 ES2021 引入的,现代浏览器都支持: - Chrome: 85+ - Firefox: 79+ - Safari: 14+ - Edge: 85+ 对于旧浏览器,可以使用 polyfill: ```javascript if (!Promise.any) { Promise.any = function(promises) { return new Promise((resolve, reject) => { const errors = []; let rejectedCount = 0; promises.forEach((promise, index) => { Promise.resolve(promise).then( value => resolve(value), error => { errors[index] = error; rejectedCount++; if (rejectedCount === promises.length) { reject(new AggregateError(errors, 'All promises were rejected')); } } ); }); if (promises.length === 0) { reject(new AggregateError(errors, 'All promises were rejected')); } }); }; } ``` ## 最佳实践 ### 1. 总是处理 AggregateError ```javascript async function fetchData(sources) { try { return await Promise.any(sources.map(s => fetch(s))); } catch (error) { if (error instanceof AggregateError) { console.error('所有数据源都失败了'); throw new Error('无法获取数据'); } throw error; } } ``` ### 2. 提供降级方案 ```javascript async function fetchWithFallback(sources, fallbackData) { try { const response = await Promise.any(sources.map(s => fetch(s))); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.warn('所有数据源都失败,使用降级数据'); return fallbackData; } throw error; } } ``` ### 3. 记录失败原因 ```javascript async function fetchWithLogging(sources) { try { return await Promise.any(sources.map(s => fetch(s))); } catch (error) { if (error instanceof AggregateError) { error.errors.forEach((err, index) => { console.error(`${sources[index]} 失败:`, err.message); }); throw new Error('无法获取数据'); } throw error; } } ``` ## 总结 1. **返回第一个成功的 Promise**:只要有一个成功就立即返回 2. **AggregateError 处理所有失败**:所有 Promise 都失败时返回 AggregateError 3. **适合多数据源场景**:从多个数据源获取数据,使用最快成功的 4. **与 Promise.race() 不同**:只返回成功的结果,不返回失败的结果 5. **与 Promise.all() 不同**:不需要全部成功,只要一个成功即可 6. **提供降级方案**:所有失败时可以提供降级数据 7. **记录失败原因**:AggregateError 包含所有失败的原因
服务端 · 2月22日 14:07