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

面试题手册

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.any() 的作用是什么?

Promise.any() 是 ES2021 引入的 Promise 静态方法,它接收一个 Promise 数组,返回第一个成功完成的 Promise 的结果。如果所有 Promise 都失败,则返回 AggregateError。基本概念Promise.any() 接收一个可迭代的 Promise 对象作为参数,返回一个新的 Promise。这个新的 Promise 会在第一个 Promise 成功完成时立即完成,返回该 Promise 的结果。如果所有 Promise 都失败,则返回一个 AggregateError,包含所有失败的原因。基本用法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(): 返回第一个成功的 Promiseconst 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(无论成功或失败)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)); // 输出: 错误1Promise.any() vs Promise.all()Promise.any(): 只要有一个成功就返回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(): 必须全部成功才返回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)); // 输出: 错误1Promise.any() vs Promise.allSettled()Promise.any(): 返回第一个成功的结果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 的状态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,包含所有失败的原因。基本用法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'] });处理 AggregateErrorasync 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. 从多个数据源获取数据,使用最快成功的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. 实现超时机制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. 尝试多个备份方案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. 图片加载,使用第一个成功加载的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));错误处理处理所有失败的情况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; }}记录所有失败的原因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; }}性能考虑空数组处理Promise.any([]) .then(result => console.log(result)) .catch(error => { console.error(error instanceof AggregateError); // true console.error(error.errors); // [] });非 Promise 值处理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: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. 总是处理 AggregateErrorasync 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. 提供降级方案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. 记录失败原因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; }}总结返回第一个成功的 Promise:只要有一个成功就立即返回AggregateError 处理所有失败:所有 Promise 都失败时返回 AggregateError适合多数据源场景:从多个数据源获取数据,使用最快成功的与 Promise.race() 不同:只返回成功的结果,不返回失败的结果与 Promise.all() 不同:不需要全部成功,只要一个成功即可提供降级方案:所有失败时可以提供降级数据记录失败原因:AggregateError 包含所有失败的原因
阅读 0·2月22日 14:07

如何优化 RPC 调用的性能?有哪些减少网络延迟的方法?

RPC 调用中,网络延迟和性能优化是关键问题,需要从多个层面进行优化:1. 连接池优化长连接复用:避免频繁建立和断开连接连接池大小:根据并发量合理配置连接池连接预热:启动时预先建立连接连接保活:定期发送心跳保持连接活跃实现示例: // Netty 连接池配置 EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.TCP_NODELAY, true);2. 序列化优化选择高效序列化协议:Protobuf > Thrift > Hessian > JSON减少序列化数据量:使用字段编号而非字段名避免序列化不必要的数据使用压缩算法(如 Gzip、Snappy)对象池技术:复用序列化对象,减少 GC 压力3. 网络传输优化TCP 参数调优:TCP_NODELAY:禁用 Nagle 算法,减少延迟SO_KEEPALIVE:启用 TCP 保活SO_RCVBUF/SO_SNDBUF:调整接收和发送缓冲区大小HTTP/2 多路复用:减少连接数,提高并发性能批量请求:合并多个小请求,减少网络往返4. 负载均衡优化就近原则:选择网络延迟最低的服务实例权重分配:根据实例性能分配不同权重健康检查:快速剔除故障实例实现示例: // Dubbo 权重负载均衡 <dubbo:reference loadbalance="random"/>5. 缓存策略客户端缓存:缓存频繁调用的结果服务端缓存:缓存计算结果,减少重复计算分布式缓存:使用 Redis 等分布式缓存缓存失效:合理设置缓存过期时间6. 异步调用非阻塞调用:避免线程阻塞等待响应Future/Promise:异步获取结果响应式编程:使用 RxJava、Reactor 等实现示例: // gRPC 异步调用 stub.sayHello(request, new StreamObserver<HelloResponse>() { @Override public void onNext(HelloResponse response) { // 处理响应 } @Override public void onError(Throwable t) { // 处理错误 } @Override public void onCompleted() { // 调用完成 } });7. 代码优化减少不必要的字段:只传输必要的数据使用基本类型:避免使用包装类型避免大对象传输:分批传输大数据压缩传输:启用数据压缩8. 监控和调优性能监控:监控调用耗时、成功率、QPS链路追踪:使用 Zipkin、Jaeger 追踪调用链日志分析:分析慢调用日志性能测试:定期进行压力测试9. 服务端优化线程池优化:合理配置线程池大小I/O 模型:使用 Netty 等高性能 I/O 框架零拷贝:使用 FileChannel.transferTo 减少数据拷贝JVM 调优:优化 GC 参数10. 架构优化服务拆分:合理拆分服务,减少单个服务负载读写分离:分离读写操作,提高并发能力CDN 加速:静态资源使用 CDN边缘计算:将计算下沉到边缘节点性能指标:P99 延迟:99% 请求的响应时间QPS:每秒查询数TPS:每秒事务数吞吐量:单位时间处理的数据量
阅读 0·2月22日 14:07

Promise.allSettled() 的作用是什么?与 Promise.all() 有什么区别?

Promise.allSettled() 是 ES2020 引入的 Promise 静态方法,它允许我们等待所有 Promise 完成(无论成功或失败),并返回每个 Promise 的状态和结果。基本概念Promise.allSettled() 接收一个 Promise 数组作为参数,返回一个新的 Promise。这个新的 Promise 会在所有输入的 Promise 都完成(无论成功或失败)后才完成,返回一个包含所有 Promise 状态和结果的数组。基本用法const promise1 = Promise.resolve(42);const promise2 = Promise.reject('出错了');const promise3 = new Promise(resolve => setTimeout(() => resolve('延迟完成'), 1000));Promise.allSettled([promise1, promise2, promise3]) .then(results => { console.log(results); // 输出: // [ // { status: 'fulfilled', value: 42 }, // { status: 'rejected', reason: '出错了' }, // { status: 'fulfilled', value: '延迟完成' } // ] });返回值结构每个结果对象包含两个属性:status: 'fulfilled'(成功)或 'rejected'(失败)value: 成功时的值(仅当 status 为 'fulfilled' 时存在)reason: 失败时的原因(仅当 status 为 'rejected' 时存在)与 Promise.all() 的对比Promise.all() - 快速失败const promise1 = Promise.resolve(1);const promise2 = Promise.reject('错误');const promise3 = Promise.resolve(3);Promise.all([promise1, promise2, promise3]) .then(results => console.log(results)) .catch(error => console.error('错误:', error)); // 输出: 错误: 错误 // promise3 的结果无法获取Promise.allSettled() - 等待全部const promise1 = Promise.resolve(1);const promise2 = Promise.reject('错误');const promise3 = Promise.resolve(3);Promise.allSettled([promise1, promise2, promise3]) .then(results => { results.forEach(result => { if (result.status === 'fulfilled') { console.log('成功:', result.value); } else { console.error('失败:', result.reason); } }); }); // 输出: // 成功: 1 // 失败: 错误 // 成功: 3实际应用场景1. 批量请求,部分失败不影响其他结果async function fetchMultipleUrls(urls) { const promises = urls.map(url => fetch(url).then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) ); const results = await Promise.allSettled(promises); const successful = results .filter(result => result.status === 'fulfilled') .map(result => result.value); const failed = results .filter(result => result.status === 'rejected') .map(result => result.reason); console.log('成功的请求:', successful.length); console.log('失败的请求:', failed.length); return { successful, failed };}// 使用示例const urls = [ 'https://api.example.com/users', 'https://api.example.com/posts', 'https://api.example.com/comments'];fetchMultipleUrls(urls).then(({ successful, failed }) => { console.log('成功数据:', successful); console.log('失败原因:', failed);});2. 并行处理多个任务,收集所有结果async function processTasks(tasks) { const results = await Promise.allSettled( tasks.map(task => task()) ); return results.map((result, index) => ({ task: tasks[index].name, status: result.status, result: result.status === 'fulfilled' ? result.value : result.reason }));}// 使用示例const tasks = [ { name: '任务1', fn: () => Promise.resolve('完成') }, { name: '任务2', fn: () => Promise.reject('失败') }, { name: '任务3', fn: () => Promise.resolve('完成') }];processTasks(tasks).then(results => { console.table(results);});3. 图片加载,显示成功和失败的图片async function loadImages(imageUrls) { const imagePromises = imageUrls.map(url => { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve({ url, img }); img.onerror = () => reject(new Error(`Failed to load: ${url}`)); img.src = url; }); }); const results = await Promise.allSettled(imagePromises); const loadedImages = results .filter(result => result.status === 'fulfilled') .map(result => result.value); const failedUrls = results .filter(result => result.status === 'rejected') .map(result => result.reason.message); return { loadedImages, failedUrls };}// 使用示例const imageUrls = [ 'https://example.com/image1.jpg', 'https://example.com/image2.jpg', 'https://example.com/image3.jpg'];loadImages(imageUrls).then(({ loadedImages, failedUrls }) => { console.log('加载成功的图片:', loadedImages); console.log('加载失败的图片:', failedUrls);});错误处理处理部分失败async function fetchWithPartialFailure(urls) { const results = await Promise.allSettled( urls.map(url => fetch(url)) ); const successfulRequests = results .filter(result => result.status === 'fulfilled') .map(result => result.value); const failedRequests = results .filter(result => result.status === 'rejected') .map(result => result.reason); if (failedRequests.length > 0) { console.warn('部分请求失败:', failedRequests); } return successfulRequests;}重试失败的请求async function fetchWithRetry(urls, maxRetries = 3) { const results = await Promise.allSettled( urls.map(url => fetch(url)) ); const failedUrls = results .filter(result => result.status === 'rejected') .map((result, index) => urls[index]); if (failedUrls.length > 0) { console.log(`重试失败的请求 (${maxRetries} 次)`); const retryResults = await Promise.allSettled( failedUrls.map(url => fetch(url)) ); // 处理重试结果... } return results;}性能考虑1. 空数组处理Promise.allSettled([]) .then(results => console.log(results)); // 输出: []2. 非 Promise 值处理Promise.allSettled([1, 2, Promise.resolve(3)]) .then(results => console.log(results)); // 输出: // [ // { status: 'fulfilled', value: 1 }, // { status: 'fulfilled', value: 2 }, // { status: 'fulfilled', value: 3 } // ]与其他 Promise 方法的对比| 方法 | 行为 | 使用场景 ||------|------|----------|| Promise.all() | 全部成功才成功,一个失败就失败 | 需要所有结果都成功 || Promise.allSettled() | 等待全部完成,返回所有结果 | 需要知道每个操作的结果 || Promise.race() | 返回第一个完成的结果 | 需要最快的结果 || Promise.any() | 返回第一个成功的结果 | 需要任何一个成功的结果 |最佳实践1. 检查所有结果async function checkAllResults(promises) { const results = await Promise.allSettled(promises); const hasFailures = results.some(result => result.status === 'rejected'); if (hasFailures) { const failures = results .filter(result => result.status === 'rejected') .map(result => result.reason); console.error('部分操作失败:', failures); } return results;}2. 聚合结果async function aggregateResults(promises) { const results = await Promise.allSettled(promises); return { total: results.length, successful: results.filter(r => r.status === 'fulfilled').length, failed: results.filter(r => r.status === 'rejected').length, results: results };}3. 提供降级方案async function fetchWithFallback(urls, fallbackData) { const results = await Promise.allSettled( urls.map(url => fetch(url).then(r => r.json())) ); const data = results.map((result, index) => { if (result.status === 'fulfilled') { return result.value; } else { console.warn(`请求 ${urls[index]} 失败,使用降级数据`); return fallbackData[index]; } }); return data;}浏览器兼容性Promise.allSettled() 是 ES2020 引入的,现代浏览器都支持:Chrome: 76+Firefox: 71+Safari: 13+Edge: 79+对于旧浏览器,可以使用 polyfill:if (!Promise.allSettled) { Promise.allSettled = function(promises) { return Promise.all( promises.map(promise => Promise.resolve(promise).then( value => ({ status: 'fulfilled', value }), reason => ({ status: 'rejected', reason }) ) ) ); };}总结等待所有 Promise 完成:无论成功或失败,都会等待所有 Promise 完成返回详细结果:每个 Promise 的状态和结果都会被返回适合部分失败场景:当某些操作失败不影响其他操作时使用与 Promise.all() 互补:Promise.all() 快速失败,Promise.allSettled() 等待全部提供更好的错误处理:可以针对每个失败的操作进行单独处理支持 polyfill:旧浏览器可以通过 polyfill 实现兼容
阅读 0·2月22日 14:07

Promise 和回调函数的区别是什么?

Promise 和回调函数(Callback)都是 JavaScript 中处理异步操作的方式,但它们在设计理念、使用方式和代码可读性上有显著差异。回调函数基本概念回调函数是将一个函数作为参数传递给另一个函数,在异步操作完成后被调用。基本用法function fetchData(callback) { setTimeout(() => { const data = { name: 'John', age: 30 }; callback(null, data); }, 1000);}fetchData((error, data) => { if (error) { console.error('出错了:', error); return; } console.log('数据:', data);});回调地狱问题// 回调地狱:代码嵌套过深,难以维护fetch('/api/user', (error, userResponse) => { if (error) { console.error(error); return; } userResponse.json((error, user) => { if (error) { console.error(error); return; } fetch(`/api/posts/${user.id}`, (error, postsResponse) => { if (error) { console.error(error); return; } postsResponse.json((error, posts) => { if (error) { console.error(error); return; } console.log('用户文章:', posts); }); }); });});Promise基本概念Promise 是一个代表异步操作最终完成或失败的对象,提供了一种更优雅的方式来处理异步操作。基本用法function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { const data = { name: 'John', age: 30 }; resolve(data); }, 1000); });}fetchData() .then(data => console.log('数据:', data)) .catch(error => console.error('出错了:', error));链式调用// 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. 代码可读性回调函数:// 嵌套过深,难以阅读doSomething1((result1) => { doSomething2(result1, (result2) => { doSomething3(result2, (result3) => { doSomething4(result3, (result4) => { console.log(result4); }); }); });});Promise:// 扁平清晰,易于阅读doSomething1() .then(result1 => doSomething2(result1)) .then(result2 => doSomething3(result2)) .then(result3 => doSomething4(result3)) .then(result4 => console.log(result4));2. 错误处理回调函数:// 错误处理分散,容易遗漏function fetchData(callback) { setTimeout(() => { if (Math.random() > 0.5) { callback(new Error('请求失败')); } else { callback(null, { data: 'success' }); } }, 1000);}fetchData((error, data) => { if (error) { console.error('错误:', error); return; } console.log('数据:', data);});Promise:// 错误处理集中,易于管理function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { reject(new Error('请求失败')); } else { resolve({ data: 'success' }); } }, 1000); });}fetchData() .then(data => console.log('数据:', data)) .catch(error => console.error('错误:', error));3. 状态管理回调函数:没有明确的状态概念回调可能被多次调用难以追踪异步操作的状态Promise:有明确的三种状态:pending、fulfilled、rejected状态一旦改变就不可逆可以随时查询 Promise 的状态const promise = new Promise((resolve) => { setTimeout(() => resolve('完成'), 1000);});console.log(promise); // Promise {<pending>}setTimeout(() => { console.log(promise); // Promise {<fulfilled>: '完成'}}, 1500);4. 并行处理回调函数:// 难以并行处理多个异步操作function fetchAllData(callback) { let completed = 0; const results = []; const urls = ['/api/user', '/api/posts', '/api/comments']; urls.forEach((url, index) => { fetch(url, (error, data) => { if (error) { callback(error); return; } results[index] = data; completed++; if (completed === urls.length) { callback(null, results); } }); });}Promise:// 轻松并行处理多个异步操作Promise.all([ fetch('/api/user'), fetch('/api/posts'), fetch('/api/comments')]) .then(responses => Promise.all(responses.map(r => r.json()))) .then(results => console.log('所有数据:', results)) .catch(error => console.error('错误:', error));5. 组合和复用回调函数:// 难以组合和复用function fetchUser(id, callback) { setTimeout(() => callback(null, { id, name: 'John' }), 1000);}function fetchPosts(userId, callback) { setTimeout(() => callback(null, [{ id: 1, title: 'Post 1' }]), 1000);}// 组合使用需要嵌套fetchUser(1, (error, user) => { if (error) return; fetchPosts(user.id, (error, posts) => { if (error) return; console.log({ user, posts }); });});Promise:// 易于组合和复用function fetchUser(id) { return Promise.resolve({ id, name: 'John' });}function fetchPosts(userId) { return Promise.resolve([{ id: 1, title: 'Post 1' }]);}// 组合使用清晰明了fetchUser(1) .then(user => Promise.all([user, fetchPosts(user.id)])) .then(([user, posts]) => console.log({ user, posts }));转换关系回转 Promise// 将回调函数转换为 Promisefunction promisify(fn) { return function(...args) { return new Promise((resolve, reject) => { fn(...args, (error, result) => { if (error) { reject(error); } else { resolve(result); } }); }); };}// 使用示例const readFile = promisify(fs.readFile);readFile('file.txt') .then(data => console.log(data)) .catch(error => console.error(error));Promise 转回调// 将 Promise 转换为回调函数function callbackify(promiseFn) { return function(...args) { const callback = args.pop(); promiseFn(...args) .then(result => callback(null, result)) .catch(error => callback(error)); };}// 使用示例const fetchDataCallback = callbackify(fetchData);fetchDataCallback((error, data) => { if (error) { console.error(error); return; } console.log(data);});性能对比内存占用回调函数:内存占用较小不需要创建额外的 Promise 对象Promise:内存占用略高每个 Promise 都是一个对象,需要额外的内存执行效率回调函数:执行效率略高没有额外的 Promise 对象创建和微任务调度Promise:执行效率略低需要创建 Promise 对象和调度微任务实际影响// 回调函数console.time('callback');for (let i = 0; i < 10000; i++) { setTimeout(() => {}, 0);}console.timeEnd('callback');// Promiseconsole.time('promise');for (let i = 0; i < 10000; i++) { Promise.resolve();}console.timeEnd('promise');使用场景适合使用回调函数的场景Node.js 核心模块:fs、http 等模块使用回调函数一次性简单操作:简单的异步操作不需要 Promise 的复杂性性能敏感场景:需要极致性能的场景// Node.js 文件读取fs.readFile('file.txt', 'utf8', (error, data) => { if (error) { console.error(error); return; } console.log(data);});适合使用 Promise 的场景复杂的异步流程:多个异步操作需要组合需要错误处理:需要集中处理错误现代 JavaScript 开发:async/await 语法糖前端开发:fetch API、现代浏览器 API// 使用 async/awaitasync function fetchData() { try { const user = await fetchUser(); const posts = await fetchPosts(user.id); return { user, posts }; } catch (error) { console.error('错误:', error); throw error; }}总结| 特性 | 回调函数 | Promise ||------|----------|---------|| 代码可读性 | 容易产生回调地狱 | 链式调用,代码扁平 || 错误处理 | 分散,容易遗漏 | 集中,易于管理 || 状态管理 | 无明确状态 | 三种明确状态 || 并行处理 | 需要手动管理 | Promise.all 等方法 || 组合复用 | 难以组合 | 易于组合 || 性能 | 略高 | 略低 || 内存占用 | 较小 | 略高 || 现代支持 | 传统方式 | 现代标准 |最佳实践优先使用 Promise:在现代 JavaScript 开发中,优先使用 Promise使用 async/await:让异步代码更像同步代码处理错误:总是添加错误处理避免嵌套:保持代码扁平合理使用工具:使用 Promise.all、Promise.race 等方法考虑性能:在性能敏感的场景,可以考虑回调函数
阅读 0·2月22日 14:07

RPC 和 RESTful API 有什么区别?在什么场景下选择 RPC?

RPC 和 RESTful API 是两种常见的分布式系统通信方式,它们各有优缺点:RPC(远程过程调用)特点:优势:高性能:使用二进制序列化(如 Protobuf),传输效率高强类型:通过 IDL(接口定义语言)定义服务契约,编译时检查低延迟:支持 HTTP/2 多路复用,减少连接开销双向流:支持双向流式通信,适合实时场景代码生成:自动生成客户端和服务端代码,减少开发工作量劣势:调试困难:二进制协议不易直接查看和调试学习曲线:需要学习特定的 RPC 框架和协议浏览器兼容性:部分 RPC 协议(如 gRPC)需要额外支持耦合度较高:客户端和服务端需要共享接口定义RESTful API 特点:优势:简单易用:基于 HTTP 协议,使用 JSON/XML 格式通用性强:跨平台、跨语言,浏览器原生支持易于调试:可以使用 curl、Postman 等工具直接测试松耦合:客户端和服务端通过 URL 和 HTTP 方法交互缓存友好:利用 HTTP 缓存机制,提高性能劣势:性能较低:文本格式(JSON)传输效率不如二进制冗余数据:每个请求都包含 HTTP 头,增加传输开销无状态:需要额外的机制维护会话状态实时性差:不适合需要实时双向通信的场景选择建议:内部微服务通信:优先选择 RPC(如 gRPC、Dubbo)对外 API:优先选择 RESTful API实时通信场景:选择支持流的 RPC 框架跨语言团队协作:考虑 RESTful API 的通用性性能敏感场景:选择 RPC 以获得更好的性能
阅读 0·2月22日 14:07

如何选择合适的 RPC 框架?主流 RPC 框架(gRPC、Dubbo、Thrift 等)的对比和选择建议是什么?

RPC 框架的选择需要根据项目需求、技术栈、团队经验等多方面因素综合考虑:主流 RPC 框架对比:1. gRPC特点:Google 开源,基于 HTTP/2 和 Protobuf优势:高性能:HTTP/2 多路复用,Protobuf 高效序列化跨语言:支持 10+ 种语言流式通信:支持单向流和双向流强类型:IDL 定义接口,编译时检查生态完善:拦截器、负载均衡、链路追踪劣势:浏览器支持有限(需要 grpc-web)学习曲线较陡调试相对困难(二进制协议)适用场景:微服务内部通信需要流式通信的场景跨语言服务调用高性能要求的场景技术栈:Go、Java、Python、C++、Node.js 等2. Dubbo特点:阿里巴巴开源,Java 生态优势:易用性:与 Spring 深度集成功能全面:服务治理、负载均衡、容错机制性能优秀:基于 Netty,支持长连接社区活跃:阿里巴巴和社区持续维护文档完善:中文文档丰富劣势:主要面向 Java跨语言支持相对较弱适用场景:Java 微服务架构国内企业项目需要完善服务治理的场景技术栈:Java、Spring Boot、Spring Cloud Alibaba3. Thrift特点:Facebook 开源,支持多种协议和传输方式优势:跨语言:支持多种编程语言灵活性:支持多种序列化格式和传输协议代码生成:强大的代码生成功能性能优秀:二进制序列化效率高劣势:学习曲线较陡文档相对较少社区活跃度不如 gRPC适用场景:跨语言、多协议的复杂场景需要灵活配置的场景技术栈:Java、Python、Go、C++、Node.js 等4. Spring Cloud OpenFeign特点:基于 HTTP 的声明式 RPC优势:简单易用:声明式接口定义Spring 集成:与 Spring Cloud 深度集成通用性强:基于 HTTP,跨平台易于调试:文本协议,易于查看劣势:性能相对较低(基于 HTTP/1.x)不支持流式通信适用场景:Spring Cloud 微服务架构对外 API性能要求不高的场景技术栈:Java、Spring Boot、Spring Cloud5. Motan特点:微博开源,Java RPC 框架优势:简单易用:配置简单性能优秀:基于 Netty支持多种协议:RPC、HTTP服务治理:支持服务注册、发现、负载均衡劣势:社区相对较小主要面向 Java适用场景:Java 微服务架构需要简单易用的 RPC 框架技术栈:Java、Spring Boot6. brpc特点:百度开源,C++ RPC 框架优势:高性能:C++ 实现,性能优秀功能全面:支持多种协议、服务治理跨语言:支持多语言客户端劣势:主要面向 C++学习曲线较陡适用场景:C++ 微服务架构高性能要求的场景技术栈:C++、Java、Python、Go 等选择建议:1. 根据技术栈选择Java 生态:Dubbo、Spring Cloud OpenFeign、MotanGo 生态:gRPC、ThriftPython 生态:gRPC、ThriftC++ 生态:gRPC、brpc、Thrift多语言:gRPC、Thrift2. 根据性能要求选择高性能:gRPC、Dubbo、brpc一般性能:Thrift、Motan低性能要求:Spring Cloud OpenFeign3. 根据功能需求选择需要流式通信:gRPC需要完善服务治理:Dubbo、gRPC需要简单易用:Spring Cloud OpenFeign、Motan需要灵活配置:Thrift4. 根据团队经验选择熟悉 Spring:Dubbo、Spring Cloud OpenFeign熟悉 Google 技术:gRPC熟悉 Facebook 技术:Thrift5. 根据项目场景选择内部微服务:gRPC、Dubbo对外 API:Spring Cloud OpenFeign、RESTful API实时通信:gRPC跨语言:gRPC、Thrift性能对比(大致排序):序列化性能:Protobuf (gRPC) > Hessian (Dubbo) > Thrift > JSON (Feign)传输性能:HTTP/2 (gRPC) > TCP (Dubbo) > HTTP/1.x (Feign)综合性能:gRPC > Dubbo > brpc > Thrift > Motan > Feign代码示例对比:gRPC:service UserService { rpc GetUser (GetUserRequest) returns (GetUserResponse) {}}Dubbo:public interface UserService { User getUser(Long id);}Feign:@FeignClient(name = "user-service")public interface UserService { @GetMapping("/user/{id}") User getUser(@PathVariable("id") Long id);}最佳实践:优先选择社区活跃、文档完善的框架考虑团队技术栈和学习成本评估性能和功能需求考虑未来扩展性进行性能测试验证参考行业最佳实践
阅读 0·2月22日 14:06

RPC 调用中的容错机制有哪些?如何处理网络异常和服务故障?

RPC 调用过程中,网络异常、服务故障等问题不可避免,需要完善的容错机制来保证系统稳定性:1. 超时机制(Timeout)作用:防止客户端无限等待实现:设置合理的超时时间(连接超时、读取超时)策略:根据网络状况和业务需求动态调整示例:Dubbo 的 timeout 配置、gRPC 的 deadline2. 重试机制(Retry)适用场景:网络抖动、临时性故障重试策略:指数退避(Exponential Backoff):每次重试间隔逐渐增加固定间隔:每次重试间隔相同最大重试次数:避免无限重试注意事项:幂等性设计,避免重复执行导致数据不一致3. 熔断机制(Circuit Breaker)原理:当故障率达到阈值时,快速失败,避免雪崩状态:关闭(Closed)、开启(Open)、半开启(Half-Open)实现:Hystrix、Resilience4j、Sentinel参数配置:失败率阈值、超时时间、恢复时间4. 限流机制(Rate Limiting)目的:保护服务不被过载算法:令牌桶(Token Bucket)漏桶(Leaky Bucket)固定窗口(Fixed Window)滑动窗口(Sliding Window)实现:Guava RateLimiter、Redis + Lua5. 服务降级(Fallback)作用:服务不可用时提供备用方案策略:返回默认值返回缓存数据调用备用服务返回友好错误提示6. 负载均衡(Load Balancing)算法:轮询(Round Robin)随机(Random)最少连接(Least Connections)一致性哈希(Consistent Hash)健康检查:定期检测服务实例健康状态7. 服务注册与发现作用:动态管理服务实例实现:Consul、Etcd、Zookeeper、Nacos特性:健康检查、服务剔除、自动注册8. 链路追踪(Distributed Tracing)作用:快速定位问题实现:Zipkin、Jaeger、SkyWalking信息:请求 ID、调用链路、耗时统计最佳实践:组合使用多种容错机制根据业务重要性配置不同的容错策略监控和告警及时发现问题定期演练故障场景
阅读 0·2月22日 14:06

RPC 调用中的安全性问题有哪些?如何实现身份认证、数据加密和授权?

RPC 调用涉及网络传输,安全性是必须考虑的重要问题。以下是 RPC 安全性的关键方面和实现方法:1. 身份认证(Authentication)Token 认证客户端在请求中携带 Token服务端验证 Token 有效性Token 可以是 JWT、OAuth2 等实现示例: // gRPC 拦截器实现 Token 认证 public class AuthInterceptor implements ServerInterceptor { @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall( ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { String token = headers.get(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER)); if (!validateToken(token)) { call.close(Status.UNAUTHENTICATED.withDescription("Invalid token"), headers); return new ServerCall.Listener<ReqT>() {}; } return next.startCall(call, headers); } }API Key 认证为每个客户端分配唯一的 API Key简单但安全性相对较低适合内部服务调用双向 TLS(mTLS)客户端和服务端都验证对方证书提供强身份认证适用于高安全要求的场景2. 数据加密(Encryption)传输层加密TLS/SSL:加密整个通信通道HTTPS:基于 HTTP 的 RPC 使用 HTTPSgRPC over TLS:gRPC 支持 TLS 加密实现示例: // gRPC TLS 配置 NettyChannelBuilder.forAddress(host, port) .sslContext(GrpcSslContexts.forClient() .trustManager(new File("ca.pem")) .build()) .build();应用层加密对敏感数据进行额外加密使用 AES、RSA 等加密算法即使传输层被破解,数据仍然安全3. 授权(Authorization)基于角色的访问控制(RBAC)为用户分配角色角色关联权限检查用户是否有权限调用特定服务基于资源的访问控制细粒度控制对资源的访问可以控制到方法级别权限注解使用注解标记需要权限的方法拦截器统一处理权限检查4. 防重放攻击时间戳验证请求中包含时间戳服务端验证时间戳是否在有效范围内防止旧请求被重放Nonce 机制每次请求使用唯一的随机数服务端记录已使用的 Nonce防止相同请求被重复使用请求签名对请求参数进行签名签名包含时间戳和 Nonce服务端验证签名有效性5. 防止 DDoS 攻击限流限制单个客户端的请求频率使用令牌桶、漏桶等算法实现示例: // Guava RateLimiter RateLimiter rateLimiter = RateLimiter.create(100); // 100 QPS if (rateLimiter.tryAcquire()) { // 处理请求 } else { throw new RateLimitExceededException(); }黑名单/白名单拦截来自黑名单 IP 的请求只允许白名单 IP 访问验证码对可疑请求要求验证码防止自动化攻击6. 数据完整性消息认证码(MAC)使用 HMAC 等算法验证消息完整性防止数据在传输中被篡改数字签名使用私钥签名,公钥验证提供不可抵赖性7. 安全审计日志记录记录所有 RPC 调用包括调用者、时间、参数等便于事后审计和问题排查监控告警监控异常调用模式及时发现安全威胁8. 安全配置最佳实践最小权限原则只授予必要的权限定期审查权限配置定期更新证书及时更新过期的证书使用证书自动管理工具安全配置检查定期进行安全扫描使用安全配置检查工具敏感信息保护不在日志中记录敏感信息使用配置中心管理密钥定期轮换密钥9. 框架特定安全配置gRPC 安全启用 TLS使用拦截器实现认证和授权配置 ALTS(Application Layer Transport Security)Dubbo 安全配置 Token 认证使用 Dubbo Filter 实现安全检查支持自定义序列化协议加密Thrift 安全使用 TSSLTransport实现 TProcessor 拦截器自定义协议层加密
阅读 0·2月22日 14:06