面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

前端阅读 02026年5月30日 02:24

Promise 错误处理面试怎么答?

Promise 错误处理要抓住两句话:错误会沿 Promise 链向后传播,最近的 catch 或 then 第二个参数会接住它;catch 返回普通值表示恢复,重新 throw 才会继续失败。项目里一般推荐在链尾统一 catch,async/await 用 try/catch;多个任务用 Promise.allSettled 处理部分失败,避免一个请求失败拖垮全部结果。追问catch 和 then 的第二个参数有什么区别?then 的第二个参数只能处理前一个 Promise 的失败,抓不到同一个 then 成功回调里新抛出的错误;catch 更适合放在链尾统一兜底。catch 里 return 和 throw 有什么区别?return 会把链恢复成 fulfilled,后面的 then 会继续走;throw 或 return rejected Promise 才会让后续 catch 接着处理。Promise.all 里一个失败怎么办?Promise.all 会快速失败,只要一个 reject 整体就 reject。需要拿到每个任务结果时,用 Promise.allSettled,或给每个任务单独 catch。未捕获的 Promise 错误怎么排查?浏览器看 unhandledrejection,Node 看 unhandledRejection 日志。根因通常是忘记 await、忘记 return Promise,或 catch 里吞错。写段代码async function loadAll(tasks) { const results = await Promise.allSettled(tasks.map(t => t())); return results.map(r => r.status === 'fulfilled' ? r.value : { error: r.reason.message } );}
前端阅读 02026年5月30日 02:24

Promise 并发控制如何实现?

Promise 并发控制就是限制同一时间运行的异步任务数量。面试可以先说结论:维护一个任务池,启动任务后放入 executing,数量达到上限就 await Promise.race(executing),等任意任务结束再继续放新任务。最后用 Promise.all 收集全部结果。真实项目里常用于批量请求、上传、爬取、发邮件,目的是保护浏览器连接数、服务端限流和内存。追问并发控制和 Promise.all 有什么区别?Promise.all 会一次性启动所有任务;并发控制只同时跑固定数量。前者适合少量独立任务,后者适合几百、几千个任务。为什么用 Promise.race?它能等“最先完成的一个任务”。有任务完成后,池子空出位置,就可以继续补下一个任务。失败任务怎么处理?看业务。要快速失败就让错误抛出;要尽量完成全部任务,就给每个任务包一层 catch,返回 {status, value, reason}。并发数怎么定?没有固定答案。浏览器请求可从 4-8 开始;Node 服务要看下游 QPS、CPU、连接池和超时率,再动态调。写段代码async function pool(limit, list, worker) { const ret = []; const running = []; for (const item of list) { const p = Promise.resolve().then(() => worker(item)); ret.push(p); const e = p.finally(() => running.splice(running.indexOf(e), 1)); running.push(e); if (running.length >= limit) await Promise.race(running); } return Promise.all(ret);}
前端阅读 02026年5月30日 02:24

Promise 性能优化面试怎么答?

Promise 性能优化的核心不是“少用 Promise”,而是少制造没必要的异步层级,让能并行的任务并行,让高频请求有缓存、去重和并发限制。面试里先答三点:避免 new Promise 包一层已有 Promise;独立任务用 Promise.all 并行;批量任务别一次性打满,用队列或 p-limit 控制数量。再补一句:性能问题通常来自请求瀑布、重复请求、长链微任务和未释放的引用。追问Promise.all 一定更快吗?不一定。只有任务互不依赖时才更快;如果后一个请求依赖前一个结果,强行并行会写错逻辑。Promise.all 还会遇到一个失败就整体失败的问题。为什么不建议包一层 new Promise?已有 Promise 直接返回即可。多包一层会增加对象创建、微任务调度和错误传播复杂度,还容易漏掉 reject。请求去重怎么做?用 Map 保存进行中的 Promise,相同 key 直接复用;结束后在 finally 里删除,避免内存泄漏。长 Promise 链会慢吗?真正慢的通常不是链本身,而是链里塞了大量同步计算或无意义 then。可读性差时改 async/await,但不要把独立任务写成串行 await。写段代码const pending = new Map();function once(key, fn) { if (pending.has(key)) return pending.get(key); const p = fn().finally(() => pending.delete(key)); pending.set(key, p); return p;}async function load() { const [user, posts] = await Promise.all([ once('user', fetchUser), once('posts', fetchPosts) ]); return { user, posts };}
服务端阅读 02026年5月30日 02:24

Promise.allSettled() 有什么作用?和 Promise.all 有什么区别?

Promise.allSettled() 会等待一组 Promise 全部结束,不管成功还是失败,最后返回每个任务的状态和结果;Promise.all() 则要求全部成功,只要一个 reject 就立刻 reject。面试里一句话区分:all 适合“缺一个都不行”,allSettled 适合“尽量都跑完,再分别处理结果”。例如批量请求、批量上传、页面多个独立模块加载,更适合 allSettled。追问allSettled 返回值长什么样?每一项都有 status。成功是 { status: 'fulfilled', value },失败是 { status: 'rejected', reason }。allSettled 会吞掉错误吗?不会吞,只是把错误包装进结果数组。你仍然要检查 rejected 项,否则失败会被业务层忽略。什么时候不能用 allSettled?任务之间有强依赖时不适合。比如用户信息失败后,后续请求必须停止,这时用 all 或串行 await 更清楚。和给每个 Promise 单独 catch 有什么区别?单独 catch 可以兼容更老环境,也能自定义返回结构;allSettled 是标准化写法,语义更明确。写段代码const results = await Promise.allSettled([fetchUser(), fetchPosts()]);const ok = results.filter(r => r.status === 'fulfilled').map(r => r.value);const failed = results.filter(r => r.status === 'rejected').map(r => r.reason);
服务端阅读 02026年5月30日 02:24

Promise 和回调函数有什么区别?为什么能解决回调地狱?

Promise 和回调函数都能处理异步,但抽象层次不同。回调是“异步完成后调用你给的函数”,容易出现多层嵌套、错误分散、控制流难追踪;Promise 把异步结果封装成一个有状态对象,可以链式调用、统一 catch、组合 all/race/any/allSettled。面试里可以说:Promise 不是让异步变同步,而是让异步流程更可组合、错误更集中、代码更容易维护。追问Promise 解决了回调地狱吗?解决了一部分。链式 then 可以拉平嵌套,async/await 又进一步接近同步写法。但如果业务拆分不好,Promise 链也会写得很乱。Promise 的错误处理强在哪里?回调通常要每层传 err;Promise 的错误会沿链传播,最后一个 catch 可以兜底处理。回调还有用吗?有。事件监听、流、Node 风格 API、低层库里仍常见回调。Promise 更适合“一次性成功或失败”的异步结果。Promise 有什么代价?它会引入微任务调度和对象创建,不适合滥用在纯同步逻辑里。性能敏感代码要避免无意义包装。写段代码readFile('a.txt') .then(parse) .then(save) .catch(handleError);
服务端阅读 02026年5月30日 02:24

Promise.any() 有什么作用?和 Promise.race 有什么区别?

Promise.any() 的作用是:一组 Promise 里只要有一个成功,就立刻返回这个成功结果;只有全部失败时,才会 reject,并抛出 AggregateError。它适合“多个候选源,谁先成功用谁”的场景,比如多 CDN 拉资源、多个镜像接口兜底。面试里要强调:它忽略失败,只关心第一个成功;这和 Promise.race() 谁先 settled 就返回完全不同。追问Promise.any 和 Promise.race 最大区别是什么?race 看第一个完成,不管成功还是失败;any 看第一个成功,失败会被暂时忽略,除非全部失败。全部失败时会发生什么?会 reject 一个 AggregateError,里面的 errors 保存所有失败原因。不要只 catch 后打印 message,最好把 errors 也记录下来。它和 Promise.all 有什么区别?all 要全部成功才成功,一个失败就失败;any 只要一个成功就成功。一个适合“都要”,一个适合“有一个可用就行”。实际项目里怎么用?适合容灾兜底,不适合支付、写入、下单这类不能重复尝试的操作。并行请求多个源时也要考虑取消慢请求或控制成本。写段代码try { const data = await Promise.any([ fetch('/cdn-a/config.json'), fetch('/cdn-b/config.json') ]); console.log(await data.json());} catch (e) { console.error(e.errors);}
服务端阅读 02026年5月30日 02:24

RPC 和 RESTful API 有什么区别?什么时候选 RPC?

RPC 和 RESTful API 的核心区别是抽象不同:RPC 像调用远程函数,关注方法、参数和返回值;REST 更像操作资源,关注 URL、HTTP 方法和状态码。内部微服务、低延迟、高吞吐、强类型契约、双向流式通信,通常选 RPC,比如 gRPC、Dubbo。对外开放接口、浏览器直接访问、需要易调试和缓存语义,REST 更合适。面试里不要说谁替代谁,关键是边界:内部效率优先选 RPC,对外通用性优先选 REST。追问RPC 为什么通常性能更好?很多 RPC 框架使用二进制序列化和长连接,协议开销更小,也更容易做连接复用、流式传输和代码生成。REST 的优势是什么?它基于 HTTP 语义,curl、Postman、浏览器都好调试;URL、状态码、缓存头也更适合开放平台和前后端协作。gRPC 能直接给浏览器用吗?原生 gRPC 对浏览器不友好,通常需要 gRPC-Web 或网关转换。面向公网用户时,很多团队会在外层提供 REST。项目里怎么组合两者?常见做法是外部 REST 网关,内部服务之间用 RPC。网关负责鉴权、限流、协议转换,内部服务专注高效调用。
服务端阅读 02026年5月30日 02:24

RPC 常见序列化协议有哪些?各自怎么选?

RPC 常见序列化协议有 Protobuf、Thrift、JSON、Hessian、MessagePack 和 Avro。面试先给结论:内部高性能微服务优先 Protobuf 或 Thrift;需要可读、易调试、对外兼容用 JSON;Java 旧系统可能见到 Hessian;需要类 JSON 但更小的体积可考虑 MessagePack;大数据和日志链路常用 Avro。选择时看四件事:体积、速度、跨语言、schema 演进能力。追问Protobuf 为什么常用于 RPC?它是二进制格式,体积小、解析快,靠 .proto 定义字段和类型,跨语言代码生成成熟。缺点是调试不如 JSON 直观。Thrift 和 Protobuf 有什么区别?Thrift 更像一整套 RPC 方案,包含 IDL、协议和传输;Protobuf 更专注数据序列化,常和 gRPC 搭配使用。JSON 为什么还没被淘汰?因为它人类可读、生态通用、排查方便。对外 API、管理接口、低频调用里,调试成本往往比极致性能更重要。协议升级最容易出什么问题?字段删除、类型变更、枚举兼容最容易翻车。新增字段通常安全,变更字段语义要同时考虑新老客户端。写段代码message UserReq { int64 id = 1; string name = 2;}
服务端阅读 02026年5月30日 02:24

如何优化 RPC 调用性能并降低网络延迟?

优化 RPC 性能先看调用链路:连接、序列化、网络传输、线程模型、服务端处理和观测。面试可以先答:复用长连接,开启连接池和预热;选 Protobuf、Thrift 这类二进制协议,减少字段和大对象;小包低延迟场景开启 TCP_NODELAY;用异步调用、批量请求、就近路由和客户端缓存降低等待时间。最后用 P95/P99、错误率、QPS、线程池队列和链路追踪定位瓶颈,不要只凭感觉调参数。追问TCP_NODELAY 一定要开吗?不一定。它能减少小包等待,但可能增加包数量。低延迟 RPC 常开,吞吐优先的批量传输要压测后决定。序列化为什么影响延迟?序列化影响 CPU、对象分配和网络包大小。JSON 好调试但体积大;Protobuf 体积小、速度快,更适合内部高频调用。异步调用能让单次 RPC 更快吗?不一定降低单次网络耗时,但能减少线程阻塞,提高并发吞吐。真正耗时的服务端逻辑仍要单独优化。项目里怎么排查慢 RPC?先看 P99 和超时分布,再用 Trace 拆成客户端排队、网络、服务端处理、序列化几段。定位后再调连接池、线程池、负载均衡或缓存。写段代码bootstrap.option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 300);
服务端阅读 02026年5月30日 02:24

RPC 负载均衡算法有哪些?如何选择合适策略?

RPC 负载均衡常见算法有随机、轮询、加权随机/轮询、最少连接、最少活跃、最短响应时间、一致性哈希和 IP Hash。面试里先说选择原则:实例差不多用随机或轮询;机器配置不同用加权;请求耗时差异大用最少连接/最少活跃;需要会话保持或本地缓存命中用一致性哈希。真正落地还要配健康检查、熔断、权重动态调整,否则算法再好也会把流量打到故障节点。追问随机和轮询有什么区别?随机实现最简单,长期看分布均匀;轮询更可预测,但如果某台机器变慢,仍会按顺序分流。两者都适合实例能力接近的场景。为什么 Dubbo 默认常用加权随机?它简单、开销低,配合权重能表达机器能力差异。比普通轮询更不容易在短时间内形成固定流量节奏。一致性哈希解决什么问题?它让同一个 key 尽量落到同一台实例,适合会话、缓存、分片类场景。节点增减时只迁移少量 key,但要用虚拟节点缓解倾斜。实际项目里会踩什么坑?只看请求数不看耗时会误判负载;服务注册中心实例下线不及时,会出现短时间错误流量。通常要把负载均衡和健康检查、超时、重试一起设计。写段代码ServiceInstance select(List<ServiceInstance> list) { return list.stream() .filter(ServiceInstance::isHealthy) .min(Comparator.comparingInt(ServiceInstance::getActive)) .orElseThrow();}