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

前端面试题手册

如何实现 Promise 的并发控制?

Promise 的并发控制是一个重要的性能优化技术,它允许我们限制同时执行的异步操作数量,避免资源耗尽或性能下降。为什么需要并发控制问题场景// 不推荐:同时发起大量请求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 库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. 手动实现并发控制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. 使用队列实现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. 带重试机制的并发控制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. 带超时的并发控制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. 带优先级的并发控制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. 批量下载文件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. 批量处理数据库查询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. 批量发送邮件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. 动态调整并发数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. 进度监控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. 如何选择合适的并发数?// 根据网络类型选择function getOptimalConcurrency() { const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; if (connection) { return Math.min(connection.downlink || 4, 10); } return 4; // 默认值}2. 如何处理失败的任务?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 };}总结避免资源耗尽:限制同时执行的异步操作数量提高性能:合理的并发控制可以提高整体性能灵活实现:可以根据需求实现不同的并发控制策略错误处理:结合重试、超时等机制提高可靠性进度监控:实时监控任务执行进度动态调整:根据实际情况动态调整并发数
阅读 0·2月22日 14:07

如何优化 Promise 的性能?

Promise 的性能优化是提升应用响应速度和用户体验的关键。通过合理使用 Promise 和相关技术,可以显著提高异步操作的效率。避免不必要的 Promise 包装问题示例// 不推荐:不必要的 Promise 包装function fetchData() { return new Promise((resolve) => { resolve(fetch('/api/data')); });}// 推荐:直接返回 Promisefunction fetchData() { return fetch('/api/data');}优化原因不必要的 Promise 包装会增加额外的开销,包括:创建新的 Promise 对象额外的微任务调度增加内存占用并行执行独立操作顺序执行(慢)// 不推荐:顺序执行async function fetchAllData() { const user = await fetchUser(); const posts = await fetchPosts(); const comments = await fetchComments(); return { user, posts, comments };}并行执行(快)// 推荐:并行执行async function fetchAllData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); return { user, posts, comments };}性能对比假设每个请求需要 100ms:顺序执行:300ms并行执行:100ms(3倍提升)避免过长的 Promise 链问题示例// 不推荐:过长的 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));}优化方案// 推荐:使用 async/awaitasync 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) );}合理使用缓存基本缓存实现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; });}带过期时间的缓存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()) );}请求去重基本去重实现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;}完整的去重实现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);});批量处理问题示例// 不推荐:逐个处理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, 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// 不推荐:过度使用 try/catchasync 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); }}优化方案// 推荐:合理的错误处理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; }}内存优化避免内存泄漏// 不推荐:可能导致内存泄漏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; }}优化方案// 推荐:使用 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 执行时间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' );}监控并发请求数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));}总结避免不必要的 Promise 包装:减少额外的开销并行执行独立操作:利用 Promise.all 提高性能避免过长的 Promise 链:使用 async/await 提高可读性合理使用缓存:减少重复请求实现请求去重:避免重复的网络请求批量处理数据:提高处理效率优化错误处理:避免过度嵌套的 try/catch注意内存管理:避免内存泄漏监控性能指标:及时发现性能问题选择合适的并发数:根据实际情况调整并发策略
阅读 0·2月22日 14:07

如何理解 Promise 的链式调用?

Promise 的链式调用是 Promise 最强大的特性之一,它允许我们以优雅的方式处理多个异步操作,避免了回调地狱的问题。基本概念Promise 链式调用是指通过 .then() 方法返回一个新的 Promise,从而可以连续调用多个 .then() 方法。每个 .then() 方法接收前一个 Promise 的结果作为参数,并返回一个新的 Promise。链式调用的工作原理核心机制.then() 方法总是返回一个新的 Promise前一个 .then() 的返回值会传递给下一个 .then()如果返回的是 Promise,会等待其完成后再传递结果错误会沿着链向下传递,直到被 .catch() 捕获示例代码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. 返回普通值Promise.resolve(1) .then(value => value + 1) .then(value => value * 2) .then(value => console.log(value)); // 输出: 42. 返回 PromisePromise.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)); // 输出: 43. 不返回值(返回 undefined)Promise.resolve(1) .then(value => { console.log(value); // 输出: 1 // 不返回任何值,相当于返回 undefined }) .then(value => console.log(value)); // 输出: undefined4. 抛出错误Promise.resolve(1) .then(value => { throw new Error('出错了'); }) .catch(error => { console.error(error.message); // 输出: 出错了 return '恢复后的值'; }) .then(value => console.log(value)); // 输出: 恢复后的值错误处理机制错误传递Promise.resolve() .then(() => { throw new Error('第一个错误'); }) .then(() => { console.log('这行不会执行'); }) .catch(error => { console.error('捕获错误:', error.message); return '继续执行'; }) .then(value => { console.log(value); // 输出: 继续执行 });多个 catchPromise.resolve() .then(() => { throw new Error('错误'); }) .catch(error => { console.error('第一个 catch:', error.message); throw new Error('新错误'); }) .catch(error => { console.error('第二个 catch:', error.message); });Promise 链式调用 vs 回调地狱回调地狱(不推荐)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 链式调用(推荐)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. 保持链的扁平// 不推荐:嵌套 PromisePromise.resolve() .then(() => { return Promise.resolve().then(() => { return Promise.resolve().then(() => { console.log('嵌套太深'); }); }); });// 推荐:扁平链式Promise.resolve() .then(() => {}) .then(() => {}) .then(() => console.log('扁平清晰'));2. 合理使用 finallyPromise.resolve() .then(() => console.log('执行操作')) .catch(error => console.error(error)) .finally(() => console.log('清理资源'));3. 错误处理要全面Promise.resolve() .then(data => { // 处理数据 return processData(data); }) .catch(error => { // 处理错误 console.error('处理失败:', error); // 可以返回默认值或重新抛出错误 return defaultValue; });4. 避免在 then 中创建不必要的 Promise// 不推荐Promise.resolve(1) .then(value => { return new Promise(resolve => { resolve(value + 1); }); });// 推荐Promise.resolve(1) .then(value => value + 1);常见问题1. 如何在链中传递多个值?Promise.all([promise1, promise2]) .then(([result1, result2]) => { console.log(result1, result2); });2. 如何在链中跳过某些步骤?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. 如何在链中添加日志?Promise.resolve(1) .tap(value => console.log('当前值:', value)) .then(value => value + 1) .tap(value => console.log('新值:', value));与 async/await 的对比Promise 链式调用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(更易读)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 链式调用的语法糖,让异步代码看起来更像同步代码,提高了代码的可读性。
阅读 0·2月22日 14:07

如何处理 Promise 的错误?

Promise 的错误处理是使用 Promise 时必须掌握的重要技能。正确的错误处理可以确保程序的健壮性,避免未捕获的错误导致程序崩溃。错误处理的基本方法1. 使用 .catch() 方法.catch() 是 Promise 错误处理的主要方法,它会捕获链中任何地方抛出的错误:Promise.resolve() .then(() => { throw new Error('出错了'); }) .catch(error => { console.error('捕获到错误:', error.message); });2. 在 .then() 的第二个参数中处理.then() 方法可以接收两个参数:成功回调和失败回调:Promise.resolve() .then( result => console.log('成功:', result), error => console.error('失败:', error.message) );注意:这种方式的错误处理只捕获前一个 Promise 的错误,不会捕获链中后续的错误。错误传播机制错误会沿着 Promise 链向下传播Promise.resolve() .then(() => { throw new Error('第一个错误'); }) .then(() => { console.log('这行不会执行'); }) .then(() => { console.log('这行也不会执行'); }) .catch(error => { console.error('最终捕获:', error.message); // 输出: 最终捕获: 第一个错误 });错误可以被捕获后恢复Promise.resolve() .then(() => { throw new Error('出错了'); }) .catch(error => { console.error('捕获错误:', error.message); return '恢复后的值'; // 返回一个值,链可以继续 }) .then(value => { console.log('继续执行:', value); // 输出: 继续执行: 恢复后的值 });常见错误处理场景1. 网络请求错误处理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 的错误处理Promise.all([promise1, promise2, promise3]) .then(results => { console.log('全部成功:', results); }) .catch(error => { console.error('至少一个失败:', error.message); });3. 使用 Promise.allSettled 处理部分失败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. 总是添加错误处理// 不推荐:没有错误处理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 进行清理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. 错误处理要具体// 不推荐:笼统的错误处理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. 考虑错误恢复策略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/catchasync function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); console.log('数据:', data); } catch (error) { console.error('错误:', error.message); // 错误处理逻辑 }}捕获特定错误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 错误全局错误处理// 处理未捕获的 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// 危险:没有错误处理Promise.reject('出错了') .then(result => console.log(result)); // 错误会冒泡到全局,可能导致程序崩溃// 安全:添加错误处理Promise.reject('出错了') .then(result => console.log(result)) .catch(error => console.error(error));2. 在 catch 中忘记重新抛出错误// 可能导致问题:错误被吞掉Promise.reject('出错了') .catch(error => { console.error('捕获错误:', error); // 忘记重新抛出,后续代码会继续执行 }) .then(() => { console.log('这行会执行,即使前面有错误'); });// 推荐:根据情况决定是否重新抛出Promise.reject('出错了') .catch(error => { console.error('捕获错误:', error); // 如果错误无法恢复,重新抛出 throw error; });3. 混用 then 的第二个参数和 catch// 不推荐:容易混淆Promise.resolve() .then( result => console.log('成功'), error => console.error('失败1') ) .catch(error => console.error('失败2'));// 推荐:统一使用 catchPromise.resolve() .then(result => console.log('成功')) .catch(error => console.error('失败'));总结总是添加错误处理:使用 .catch() 或 try/catch理解错误传播:错误会沿着 Promise 链向下传播使用 finally 清理:无论成功或失败都要执行的代码放在 finally 中具体化错误处理:根据错误类型进行不同的处理考虑错误恢复:实现重试机制或降级策略避免错误吞掉:确保错误被正确处理或重新抛出全局错误监控:使用全局错误处理器捕获未处理的错误
阅读 0·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 的错误。使用场景需要同时发起多个独立的请求,并等待所有请求都完成多个异步操作之间存在依赖关系,需要所有结果都准备好后才能继续示例代码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); });特点并行执行: 所有 Promise 同时开始执行顺序保证: 结果数组顺序与输入顺序一致快速失败: 任何一个失败,整体立即失败空数组: 如果传入空数组,立即返回成功Promise.race()基本概念Promise.race() 同样接收一个 Promise 数组,返回一个新的 Promise。这个新的 Promise 会在第一个 Promise 完成(无论成功或失败)时立即完成,并返回第一个完成的 Promise 的结果或错误。使用场景设置超时机制从多个数据源获取数据,使用最快返回的结果竞争条件处理示例代码// 超时示例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));特点快速返回: 返回第一个完成的结果状态继承: 成功或失败取决于第一个完成的 Promise不确定性: 哪个 Promise 先完成是不确定的空数组: 如果传入空数组,Promise 永远处于 pending 状态对比总结| 特性 | Promise.all() | Promise.race() ||------|---------------|----------------|| 完成条件 | 所有 Promise 都成功 | 第一个 Promise 完成 || 失败条件 | 任何一个 Promise 失败 | 第一个 Promise 失败 || 返回结果 | 所有结果的数组 | 第一个完成的结果 || 使用场景 | 需要所有结果 | 需要最快结果 || 执行方式 | 并行执行 | 并行执行 |其他相关方法Promise.allSettled()ES2020 新增,等待所有 Promise 完成(无论成功或失败),返回每个 Promise 的状态和结果: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:Promise.any([promise1, promise2, promise3]) .then(result => console.log('第一个成功:', result)) .catch(error => console.log('全部失败:', error));实际应用建议使用 Promise.all(): 当需要所有数据都准备好时,如加载多个必需的资源使用 Promise.race(): 当只需要最快的结果时,如设置超时或从多个镜像源获取数据使用 Promise.allSettled(): 当需要知道每个操作的结果,即使某些失败使用 Promise.any(): 当只需要一个成功的结果,如从多个备份服务器获取数据
阅读 0·2月22日 14:07

Promise 的微任务机制是什么?如何理解事件循环?

Promise 的微任务机制是 JavaScript 事件循环中的重要概念,理解它对于掌握异步编程和解决复杂的异步问题至关重要。事件循环基础JavaScript 是单线程的,通过事件循环来处理异步操作。事件循环负责协调执行栈、宏任务队列和微任务队列。事件循环的执行顺序执行同步代码(执行栈)执行所有微任务执行一个宏任务重复步骤 2-3微任务 vs 宏任务微任务(Microtask)Promise.then/catch/finallyqueueMicrotask()MutationObserver宏任务(Macrotask)setTimeoutsetIntervalsetImmediate(Node.js)I/O 操作UI 渲染执行顺序示例基本示例console.log('1. 同步代码');setTimeout(() => { console.log('2. setTimeout(宏任务)');}, 0);Promise.resolve().then(() => { console.log('3. Promise.then(微任务)');});console.log('4. 同步代码');// 输出顺序:// 1. 同步代码// 4. 同步代码// 3. Promise.then(微任务)// 2. setTimeout(宏任务)复杂示例console.log('1. 开始');setTimeout(() => { console.log('2. setTimeout 1'); Promise.resolve().then(() => { console.log('3. setTimeout 1 中的微任务'); });}, 0);Promise.resolve().then(() => { console.log('4. Promise.then 1'); setTimeout(() => { console.log('5. Promise.then 1 中的 setTimeout'); }, 0);});Promise.resolve().then(() => { console.log('6. Promise.then 2');});setTimeout(() => { console.log('7. setTimeout 2');}, 0);console.log('8. 结束');// 输出顺序:// 1. 开始// 8. 结束// 4. Promise.then 1// 6. Promise.then 2// 2. setTimeout 1// 7. setTimeout 2// 3. setTimeout 1 中的微任务// 5. Promise.then 1 中的 setTimeoutPromise 的微任务机制Promise 状态改变触发微任务const promise = new Promise((resolve) => { console.log('1. Promise 构造函数(同步)'); resolve('成功');});promise.then(() => { console.log('2. Promise.then(微任务)');});console.log('3. 同步代码');// 输出顺序:// 1. Promise 构造函数(同步)// 3. 同步代码// 2. Promise.then(微任务)链式调用的微任务Promise.resolve() .then(() => { console.log('1. 第一个 then'); return '结果'; }) .then(() => { console.log('2. 第二个 then'); }) .then(() => { console.log('3. 第三个 then'); });console.log('4. 同步代码');// 输出顺序:// 4. 同步代码// 1. 第一个 then// 2. 第二个 then// 3. 第三个 then嵌套 Promise 的微任务Promise.resolve() .then(() => { console.log('1. 外层 then'); Promise.resolve().then(() => { console.log('2. 内层 then'); }); }) .then(() => { console.log('3. 外层第二个 then'); });console.log('4. 同步代码');// 输出顺序:// 4. 同步代码// 1. 外层 then// 2. 内层 then// 3. 外层第二个 then实际应用场景1. 确保代码在下一个事件循环执行function nextTick(callback) { Promise.resolve().then(callback);}console.log('1. 开始');nextTick(() => { console.log('2. 下一个事件循环');});console.log('3. 结束');// 输出顺序:// 1. 开始// 3. 结束// 2. 下一个事件循环2. 批量更新 DOMfunction batchUpdate(updates) { // 使用微任务确保在当前事件循环结束后执行 Promise.resolve().then(() => { updates.forEach(update => { update(); }); });}// 使用示例batchUpdate([ () => document.body.style.backgroundColor = 'red', () => document.body.style.color = 'white', () => document.body.style.fontSize = '16px']);3. 避免阻塞渲染function processLargeData(data) { let index = 0; function processChunk() { const chunkSize = 1000; const end = Math.min(index + chunkSize, data.length); for (; index < end; index++) { processDataItem(data[index]); } if (index < data.length) { // 使用微任务让出控制权,避免阻塞渲染 Promise.resolve().then(processChunk); } } processChunk();}常见面试题1. Promise 和 setTimeout 的执行顺序setTimeout(() => { console.log('1. setTimeout');}, 0);Promise.resolve().then(() => { console.log('2. Promise.then');});// 输出顺序:// 2. Promise.then// 1. setTimeout原因:微任务优先级高于宏任务,会在当前宏任务执行完后立即执行。2. 多个 Promise 的执行顺序Promise.resolve() .then(() => console.log('1')) .then(() => console.log('2')) .then(() => console.log('3'));Promise.resolve() .then(() => console.log('4')) .then(() => console.log('5')) .then(() => console.log('6'));// 输出顺序:// 1// 4// 2// 5// 3// 6原因:每个 Promise 链的第一个 then 都是微任务,按照注册顺序执行。3. Promise 构造函数中的同步代码console.log('1. 开始');const promise = new Promise((resolve) => { console.log('2. Promise 构造函数'); resolve();});promise.then(() => { console.log('3. Promise.then');});console.log('4. 结束');// 输出顺序:// 1. 开始// 2. Promise 构造函数// 4. 结束// 3. Promise.then原因:Promise 构造函数中的代码是同步执行的,then 回调是微任务。性能考虑1. 避免过长的微任务链// 不推荐:过长的微任务链可能导致性能问题function processItems(items) { return items.reduce((promise, item) => { return promise.then(() => processItem(item)); }, Promise.resolve());}// 推荐:使用 Promise.all 并行处理function processItems(items) { return Promise.all(items.map(item => processItem(item)));}2. 合理使用微任务// 不推荐:不必要的微任务function doSomething() { Promise.resolve().then(() => { console.log('执行'); });}// 推荐:直接执行同步代码function doSomething() { console.log('执行');}Node.js 中的微任务Node.js 中的微任务机制与浏览器略有不同:process.nextTick(() => { console.log('1. process.nextTick');});Promise.resolve().then(() => { console.log('2. Promise.then');});setImmediate(() => { console.log('3. setImmediate');});// 输出顺序:// 1. process.nextTick// 2. Promise.then// 3. setImmediate注意:process.nextTick 的优先级高于 Promise.then。总结微任务优先级高于宏任务:微任务会在当前宏任务执行完后立即执行Promise.then 是微任务:Promise 的回调会在微任务队列中执行事件循环的执行顺序:同步代码 → 微任务 → 宏任务 → 微任务 → 宏任务…链式调用的执行顺序:每个 then 都会创建一个微任务,按顺序执行Promise 构造函数是同步的:构造函数中的代码立即执行合理使用微任务:避免不必要的微任务,注意性能影响Node.js 的差异:process.nextTick 优先级高于 Promise.then
阅读 0·2月22日 14:06

MobX 中 action 的作用和使用方法是什么?

在 MobX 中,action 是修改 observable 状态的唯一推荐方式。使用 action 可以确保状态变化是可追踪、可预测的,并且能够优化性能。Action 的作用批量更新:action 内部的所有状态变化会被批量处理,减少不必要的重新计算可追踪性:所有状态变化都集中在 action 中,便于调试和追踪事务性:action 内部的状态变化是原子的,要么全部成功,要么全部失败性能优化:减少 reaction 的触发次数,提高性能Action 的使用方式1. 使用装饰器import { observable, action } from 'mobx';class TodoStore { @observable todos = []; @action addTodo(text) { this.todos.push({ text, completed: false }); } @action.bound removeTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); } @action async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); this.todos = data; }}2. 使用 makeObservableimport { makeObservable, observable, action } from 'mobx';class TodoStore { todos = []; constructor() { makeObservable(this, { todos: observable, addTodo: action, removeTodo: action.bound, fetchTodos: action }); } addTodo(text) { this.todos.push({ text, completed: false }); } removeTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); } async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); this.todos = data; }}3. 使用 runInActionimport { observable, runInAction } from 'mobx';class TodoStore { todos = []; async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); runInAction(() => { this.todos = data; }); }}Action 的类型1. action最基础的 action 装饰器,用于包装状态修改方法。@actionupdateTodo(id, updates) { const todo = this.todos.find(t => t.id === id); if (todo) { Object.assign(todo, updates); }}2. action.bound自动绑定 this 的 action,避免在回调函数中丢失 this 上下文。@action.boundhandleClick() { this.count++;}// 使用时不需要 bind<button onClick={this.handleClick}>Click</button>3. runInAction在异步操作中修改状态时使用。async loadData() { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; });}Action 的最佳实践1. 始终在 action 中修改状态// ❌ 错误:直接修改状态class Store { @observable count = 0; increment() { this.count++; // 不是 action }}// ✅ 正确:在 action 中修改状态class Store { @observable count = 0; @action increment() { this.count++; }}2. 使用 action.bound 处理事件处理器class Component { @observable count = 0; @action.bound handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}3. 异步操作中使用 runInAction@actionasync fetchUser(id) { this.loading = true; try { const response = await fetch(`/api/users/${id}`); const data = await response.json(); runInAction(() => { this.user = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}4. 合理拆分 action// ❌ 过于复杂的 action@actionhandleComplexOperation(data) { this.loading = true; this.data = data; this.filtered = data.filter(item => item.active); this.count = this.filtered.length; this.timestamp = Date.now(); this.loading = false;}// ✅ 拆分为多个小 action@actionhandleComplexOperation(data) { this.setLoading(true); this.setData(data); this.processData(data); this.setLoading(false);}@actionsetLoading(loading) { this.loading = loading;}@actionsetData(data) { this.data = data;}@actionprocessData(data) { this.filtered = data.filter(item => item.active); this.count = this.filtered.length; this.timestamp = Date.now();}常见错误1. 在异步回调中直接修改状态// ❌ 错误@actionasync fetchData() { const data = await fetch('/api/data'); this.data = await data.json(); // 不在 action 中}// ✅ 正确@actionasync fetchData() { const data = await fetch('/api/data'); runInAction(() => { this.data = data.json(); });}2. 忘记使用 action.bound// ❌ 错误:this 可能丢失class Component { @action handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}// ✅ 正确:使用 action.boundclass Component { @action.bound handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}性能优化使用 action 批量更新:减少 reaction 触发次数避免在循环中创建 action:复用 action 函数合理使用 runInAction:只在必要时使用使用 action.bound:避免重复 bind总结action 是修改 observable 状态的唯一推荐方式使用 action 可以批量更新、提高性能、便于调试优先使用 action.bound 处理事件处理器异步操作中使用 runInAction 修改状态合理拆分复杂的 action,提高可维护性
阅读 0·2月22日 14:06

RPC 调用中的分布式事务问题如何解决?常见的分布式事务解决方案有哪些?

分布式事务是 RPC 调用中的难点问题,涉及多个服务之间的数据一致性保证:问题背景:在微服务架构中,一个业务操作可能涉及多个服务的调用,如何保证这些服务之间的数据一致性是一个挑战。分布式事务理论:1. CAP 定理一致性(Consistency):所有节点同时看到相同的数据可用性(Availability):每个请求都能得到响应分区容错性(Partition Tolerance):系统在网络分区时仍能继续运行结论:在分布式系统中,只能同时满足两个特性2. BASE 理论基本可用(Basically Available):系统保证基本可用软状态(Soft State):允许数据存在中间状态最终一致性(Eventually Consistent):经过一段时间后数据最终一致常见解决方案:1. 两阶段提交(2PC)准备阶段:协调者询问所有参与者是否可以提交提交阶段:根据参与者反馈决定提交或回滚优点:强一致性缺点:性能差,存在单点故障,阻塞适用场景:对一致性要求极高的场景2. 三阶段提交(3PC)在 2PC 基础上增加预提交阶段减少阻塞时间仍然存在性能问题3. TCC(Try-Confirm-Cancel)Try 阶段:预留资源,检查业务规则Confirm 阶段:确认执行业务操作Cancel 阶段:取消操作,释放资源优点:性能较好,最终一致性缺点:代码侵入性强,需要实现三个接口实现示例: public interface OrderService { // Try:创建订单,预扣库存 @Compensable boolean createOrder(Order order); // Confirm:确认订单 void confirmOrder(Long orderId); // Cancel:取消订单,回滚库存 void cancelOrder(Long orderId); }4. 本地消息表在本地数据库中记录消息定时任务扫描消息表并发送消息消费成功后删除记录优点:实现简单,可靠性高缺点:需要额外的存储和定时任务适用场景:异步场景,最终一致性5. 事务消息(如 RocketMQ)发送半消息执行本地事务提交或回滚消息消费者消费消息优点:性能好,支持高并发缺点:依赖消息中间件适用场景:高并发场景,最终一致性6. Saga 模式将长事务拆分为多个本地事务每个本地事务都有对应的补偿操作按顺序执行,失败时执行补偿优点:性能好,适合长事务缺点:需要设计补偿逻辑,可能产生脏读适用场景:长事务,业务流程复杂7. Seata(阿里开源)支持 AT、TCC、SAGA、XA 模式AT 模式:一阶段:解析 SQL,记录前镜像和后镜像二阶段:提交或回滚,通过镜像数据恢复优点:对业务代码侵入小缺点:需要数据库支持适用场景:Java 生态,Spring Cloud 微服务实现示例(Seata AT 模式):@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)public void createOrder(Order order) { // 扣减库存 inventoryService.deductStock(order.getProductId(), order.getQuantity()); // 创建订单 orderMapper.insert(order); // 扣减余额 accountService.deductBalance(order.getUserId(), order.getAmount());}选择建议:强一致性要求:2PC、3PC、Seata XA高并发场景:TCC、事务消息、Seata AT长事务:Saga 模式简单场景:本地消息表Java 生态:Seata 框架最终一致性可接受:TCC、Saga、事务消息注意事项:根据业务场景选择合适的方案考虑性能和一致性的平衡做好幂等性设计完善的监控和告警机制定期进行故障演练
阅读 0·2月22日 14:06

MobX 中 computed 的作用和使用场景有哪些?

在 MobX 中,computed 是基于 observable 状态自动更新的派生值,类似于 Vue 的 computed 属性。computed 值会缓存计算结果,只有当依赖的 observable 状态发生变化时才会重新计算。Computed 的特性自动缓存:计算结果会被缓存,避免重复计算懒计算:只有在被访问时才会计算自动追踪依赖:自动追踪依赖的 observable 状态自动更新:依赖的状态变化时自动重新计算纯函数:computed 应该是纯函数,不应该有副作用Computed 的使用方式1. 使用装饰器import { observable, computed } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @computed get activeTodos() { return this.todos.filter(todo => !todo.completed); } @computed get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}2. 使用 makeObservableimport { makeObservable, observable, computed } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, activeTodos: computed, filteredTodos: computed }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } get activeTodos() { return this.todos.filter(todo => !todo.completed); } get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}3. 使用 makeAutoObservable(推荐)import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } get activeTodos() { return this.todos.filter(todo => !todo.completed); } get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}4. 使用 computed 函数import { observable, computed } from 'mobx';const store = observable({ todos: [], filter: 'all'});const completedTodos = computed(() => store.todos.filter(todo => todo.completed));const activeTodos = computed(() => store.todos.filter(todo => !todo.completed));console.log(completedTodos.get()); // 获取计算值Computed 的配置选项1. name为 computed 设置名称,便于调试。@computed({ name: 'completedTodos' })get completedTodos() { return this.todos.filter(todo => todo.completed);}2. equals自定义比较函数,决定是否需要重新计算。@computed({ equals: (a, b) => a.length === b.length && a.every(item => b.includes(item))})get filteredTodos() { return this.todos.filter(todo => todo.completed);}3. keepAlive保持计算值活跃,即使没有被访问。@computed({ keepAlive: true })get expensiveComputation() { return this.largeArray.reduce((sum, item) => sum + item.value, 0);}Computed 的最佳实践1. 使用 computed 缓存复杂计算class Store { @observable items = []; @computed get totalValue() { return this.items.reduce((sum, item) => sum + item.value, 0); } @computed get averageValue() { return this.items.length > 0 ? this.totalValue / this.items.length : 0; }}2. 避免在 computed 中产生副作用// ❌ 错误:computed 中有副作用@computed get filteredItems() { console.log('Filtering items'); // 副作用 return this.items.filter(item => item.active);}// ✅ 正确:使用 reaction 处理副作用@computed get filteredItems() { return this.items.filter(item => item.active);}constructor() { reaction( () => this.filteredItems, (items) => console.log('Filtered items:', items) );}3. 合理使用 computed 链class Store { @observable products = []; @observable category = 'all'; @observable minPrice = 0; @observable maxPrice = Infinity; @computed get filteredByCategory() { return this.category === 'all' ? this.products : this.products.filter(p => p.category === this.category); } @computed get filteredByPrice() { return this.filteredByCategory.filter( p => p.price >= this.minPrice && p.price <= this.maxPrice ); } @computed get sortedProducts() { return [...this.filteredByPrice].sort((a, b) => a.price - b.price); }}4. 使用 computed 处理表单验证class FormStore { @observable username = ''; @observable email = ''; @observable password = ''; @computed get usernameError() { if (this.username.length < 3) { return 'Username must be at least 3 characters'; } return ''; } @computed get emailError() { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(this.email)) { return 'Invalid email format'; } return ''; } @computed get passwordError() { if (this.password.length < 8) { return 'Password must be at least 8 characters'; } return ''; } @computed get isValid() { return !this.usernameError && !this.emailError && !this.passwordError; }}Computed vs Reaction| 特性 | Computed | Reaction ||------|----------|----------|| 用途 | 计算派生值 | 执行副作用 || 返回值 | 返回计算结果 | 不返回值 || 缓存 | 自动缓存 | 不缓存 || 触发时机 | 被访问时 | 依赖变化时立即执行 || 适用场景 | 数据转换、过滤、聚合 | 日志、API 调用、DOM 操作 |性能优化合理使用 computed:避免过度使用,只在需要缓存时使用避免复杂计算:如果计算非常耗时,考虑使用 memoization使用 computed 链:将复杂计算拆分为多个小的 computed避免在 computed 中创建新对象:可能导致不必要的重新计算常见错误1. 在 computed 中修改状态// ❌ 错误:computed 中修改状态@computed get filteredItems() { this.lastFilterTime = Date.now(); // 修改状态 return this.items.filter(item => item.active);}// ✅ 正确:使用 reaction 处理副作用@computed get filteredItems() { return this.items.filter(item => item.active);}constructor() { reaction( () => this.filteredItems, () => { this.lastFilterTime = Date.now(); } );}2. 在 computed 中使用异步操作// ❌ 错误:computed 中使用异步操作@computed async get userData() { const response = await fetch('/api/user'); return response.json();}// ✅ 正确:使用 reaction 处理异步操作constructor() { reaction( () => this.userId, async (id) => { const response = await fetch(`/api/user/${id}`); runInAction(() => { this.userData = await response.json(); }); } );}总结computed 是基于 observable 状态自动更新的派生值computed 会自动缓存计算结果,提高性能computed 应该是纯函数,不应该有副作用合理使用 computed 链处理复杂计算避免在 computed 中修改状态或使用异步操作使用 reaction 处理副作用和异步操作
阅读 0·2月22日 14:05

MobX 中 observable 的使用方法和注意事项有哪些?

在 MobX 中,observable 是核心概念之一,它将普通的 JavaScript 对象、数组、Map、Set 等数据结构转换为可观察对象,使 MobX 能够追踪其变化。Observable 的使用方式1. 使用装饰器(Decorator)import { observable, action, computed } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @action addTodo(text) { this.todos.push({ text, completed: false }); }}2. 使用 makeObservableimport { makeObservable, observable, action, computed } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, addTodo: action }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}3. 使用 makeAutoObservable(推荐)import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}4. 使用 observable 函数import { observable } from 'mobx';const state = observable({ todos: [], filter: 'all'});const completedTodos = observable(() => state.todos.filter(todo => todo.completed));Observable 的特性1. 自动追踪依赖当 observable 对象被读取时,MobX 会自动建立依赖关系。import { observable, autorun } from 'mobx';const store = observable({ count: 0});autorun(() => { console.log('Count is:', store.count);});store.count = 1; // 自动触发 autorun2. 深度可观察observable 默认是深度可观察的,会递归地将对象的所有属性都转换为可观察。const store = observable({ user: { name: 'John', address: { city: 'New York' } }});store.user.address.city = 'Boston'; // 会被追踪3. 数组和 Map 的特殊处理observable 数组和 Map 提供了额外的 API,但保持与原生 API 兼容。const list = observable([1, 2, 3]);list.push(4); // MobX 会追踪这个变化list.splice(0, 1); // 也会被追踪Observable 配置选项1. shallow(浅层可观察)只追踪对象本身的变化,不追踪嵌套对象。import { observable } from 'mobx';const store = observable({ user: { name: 'John' }}, {}, { deep: false });store.user = { name: 'Jane' }; // 会被追踪store.user.name = 'Bob'; // 不会被追踪2. asMap、asArray显式指定数据结构类型。const map = observable.map({ key: 'value' });const array = observable.array([1, 2, 3]);注意事项不要在 action 外部修改 observable 状态避免在 constructor 中使用装饰器合理使用 computed 缓存计算结果对于大型对象,考虑使用 shallow 优化性能observable 对象不能被冻结(Object.freeze)最佳实践优先使用 makeAutoObservable 简化配置对于简单状态,使用 observable 函数对于复杂状态管理,使用类 + 装饰器或 makeObservable合理使用 computed 避免重复计算始终在 action 中修改 observable 状态
阅读 0·2月22日 14:05