手写一个限制最大并发数的异步任务调度器,怎么实现?
经典手写题:实现一个 Scheduler,最多同时运行 2 个任务,add 返回 Promise。用一个等待队列加一个运行计数器实现,附 2 3 1 4 输出的完整推演,以及同步抛错、任务失败卡死队列这些常见漏洞。
异步编程专题面试题第 3 页,显示第 101–150 题,共找到 292 道完整解析,可继续按分类、标签与关键词缩小范围。
按稳定语义路径排序
经典手写题:实现一个 Scheduler,最多同时运行 2 个任务,add 返回 Promise。用一个等待队列加一个运行计数器实现,附 2 3 1 4 输出的完整推演,以及同步抛错、任务失败卡死队列这些常见漏洞。
fetch 重试最常见的坑是参数丢失:Request 对象的请求体只能读一次,第二次发就报 TypeError,options 被拦截器改过也会带错。讲清保存 url 和 options 重发、每次 clone、哪些错误该重试、退避间隔和 POST 幂等问题。
AbortSignal.any 接收一组 signal,返回一个新 signal,其中任意一个被取消它就跟着取消。讲清用户取消加超时的组合写法、怎么通过 reason 区分是谁取消的、已取消的 signal 传进去会怎样,以及老环境下的手写降级。
Promise.all([]) 不会卡住也不会报错,而是返回一个已经 fulfilled、值为空数组的 Promise。讲清它为什么是同步变成 fulfilled、回调却仍然异步执行,以及 race、any、allSettled 遇到空数组时各自的表现和业务里的坑。
Promise.all 只要有一个请求失败就整体 reject,其他成功的结果全拿不到。讲两种保住结果的写法:给每个 Promise 单独 catch 兜底,或者改用 Promise.allSettled,以及关键接口和可选接口混在一起时该怎么拆。
Promise.all 数组里放数字、字符串、null 这类普通值不会报错,它们会被 Promise.resolve 包一层,结果数组里原样保留,位置也不变。讲清包装过程、thenable 对象的特殊处理、结果顺序为什么和完成顺序无关,以及传入不可迭代值时的表现。
Promise.all 是快速失败的:第一个 reject 出现时整体立刻拒绝,不等其他任务。但其他任务不会被停掉,它们照样执行完,结果和后续错误都被静默丢弃。附执行顺序演示、用 AbortController 真正停掉剩余请求的写法和常见误区。
Promise.allSettled 的结果数组里,成功项是 status 加 value,失败项是 status 加 reason,顺序和传入顺序一致。讲清两种结构、为什么要用 status 而不是 value 判断成败、TypeScript 里怎么收窄类型,以及和 Promise.all 的区别。
明明函数已经返回 Promise,还要 new Promise 再包一层,代码多了不说,内层出错时外层 Promise 可能永远 pending。这篇用一段能跑的代码演示错误是怎么丢的,讲清直接 return 原链的改法,以及什么时候才真的需要 new Promise。
Promise.any 只要有一个成功就返回,全部失败时才拒绝,拒绝值是 AggregateError,每个失败原因都在它的 errors 数组里。这篇讲清 errors 的顺序规则、空数组的特殊情况、多节点兜底请求的写法,以及 catch 里怎么区分 AggregateError 和普通错误。
Promise.race 取第一个结束的结果,不管成功还是失败;Promise.any 取第一个成功的,失败会被跳过,全部失败才拒绝。这篇用同一组任务对比两者的输出,讲清超时控制和多节点择优两个典型场景该选哪个,以及空数组时两者的不同表现。
catch 放在链中间,会把错误吃掉并让链恢复成功,后面的 then 照常执行;放在末尾,前面任何一步出错都会跳过剩下的 then 直接进来。这篇用一段对比代码讲清两种位置的输出差异、catch 的返回值怎么决定后续走向,以及 then 第二个参数和 catch 的区别。
then 回调里抛出的异常会被 Promise 自动捕获,变成链上的拒绝,后面的成功回调被跳过,直接进入 catch。这篇用代码演示错误沿链传递和恢复的过程,讲清只有同步抛出才会被捕获、setTimeout 里的 throw 为什么接不住,以及外层 try/catch 为什么无效。
new Promise 传入的 executor 是同步执行的,new 的那一刻就会被立即调用;异步的只是 then 回调。通过一段打印顺序题讲清 executor、resolve 和 then 各自在什么时候运行,以及 executor 里写异步代码、同步抛错时会怎样。
executor 里同步 throw 的错误会被 Promise 构造器捕获,Promise 变成 rejected,错误对象就是拒绝原因。讲清三个边界:resolve 之后再 throw 会被忽略,setTimeout 回调里 throw 构造器接不住,没写 catch 会触发未处理拒绝。
finally 回调拿不到结果,返回值也会被忽略,上一步的成功值或失败原因会原样传下去;只有在 finally 里抛错或返回 rejected Promise,才会改写结果。用可运行的示例讲清这三点,以及用 finally 关 loading 的正确姿势。
then 回调返回一个 Promise 时,下一个 then 拿到的是这个 Promise 最终的值,而不是 Promise 本身。讲清外层 Promise 如何等待并跟随内层的状态、thenable 为什么也会被展开、返回自身为什么报错,以及展开带来的额外微任务。
Promise.race 传入空数组,返回的 Promise 永远停在 pending,既不成功也不失败,await 它的代码会一直卡住。对比 Promise.all、allSettled、any 在空数组下的不同结果,讲清原因和业务代码里该怎么防。
Promise.resolve 收到带 then 方法的对象时,不会原样返回它,而是新建一个 Promise,在下一个微任务里调用它的 then 来决定结果。附原样返回的唯一条件、then 行为不规范时的处理和 await 的关系。
多个 Promise 同时跑,then 回调基本按谁先兑现谁先执行,而不是按创建顺序。但用 Promise 去 resolve 另一个 Promise 会多等两个微任务,顺序就会反过来。附 Promise.all 结果顺序和真实请求里的处理方式。
Promise 只认第一次 resolve 或 reject,之后的调用全部静默忽略,也不会报错。但 resolve 不会让函数停下,后面的代码照样执行。附 resolve 一个 Promise 时的锁定状态、超时封装里的应用和手写 Promise 的实现要点。
哪怕 Promise 早就兑现,then 回调也要等当前同步代码跑完才执行。这篇讲清它被放进了微任务队列、为什么规范要这样设计(避免时同步时异步的回调),以及和 setTimeout、await 的执行顺序关系。
用 Promise.race 让原任务和一个定时拒绝的 Promise 赛跑,就能实现超时。但 race 只是不再等结果,原任务照样执行完。附清理定时器的完整写法、用 AbortController 真正取消请求的方式和常见追问。
Promise 被拒绝后,如果到本轮微任务结束都没有任何处理函数,浏览器就会触发 unhandledrejection。这篇讲清几种最常见的漏处理写法、catch 挂错位置的坑、Node.js 里的默认行为,以及怎样用它做全局错误上报。
异步递归有两个坑:await 之前就递归会同步爆栈,每层都 return 下一层的 Promise 会让内存一直涨。这篇讲清两种问题的来源,为什么尾递归在 JavaScript 里靠不住,以及用 while 循环加队列改写的标准做法。
用 reduce 从 Promise.resolve(初始值) 开始,每一步 prev.then(fn),就能让函数依次执行并拿到上一步的结果。附 async/await 写法、把 fn() 写成立即调用导致变并发的经典错误、错误处理和收集全部结果的方法。
every 问的是有没有反例,空数组没有反例所以是 true;some 问的是有没有一个满足的,空数组一个都没有所以是 false。这篇讲清背后的逻辑、两者的短路行为、空购物车也能提交这类业务 bug,以及回调写成 async 时的坑。
Array.from 认两种输入:可迭代对象和类数组(有 length 和数字下标),数组展开只认可迭代对象,所以只有 length 的对象一展开就报 TypeError。附两者都满足时谁优先、缺失下标变成 undefined、第二个参数 mapFn 的用法。
forEach 里 return 为什么停不下来?对比 for...of 和 forEach 在跳过当前项、提前结束、处理 async 回调上的区别,以及查找用 some、find,顺序等待用 for...of,并发用 Promise.all 的选择方法。
现代浏览器直接用 await blob.arrayBuffer(),老环境用 FileReader.readAsArrayBuffer 配合 load、error 事件。讲清两种写法、怎样把 FileReader 封装成 Promise、大文件为什么要用 stream 或分片读取,以及转文本的更简单方法。
异步生成器用 async function* 声明,里面既能 await 又能 yield,next() 返回的是 Promise,要用 for await...of 来消费。结合分页拉取数据的例子讲清它和普通生成器的区别、break 时 finally 如何清理,以及它为什么不会自动并发。
经典面试题:for 循环里用 var 声明 i,setTimeout 打印出来全是 3。原因是 var 整个函数只有一个 i,回调执行时循环早就跑完了。附 let、IIFE、setTimeout 第三个参数三种修法和常见追问。
不是所有函数都有 prototype。普通函数和 class 有,箭头函数、async 函数、对象简写方法、bind 返回的函数没有;生成器有 prototype 却不能 new。讲清 prototype 和能否 new 的关系以及常见误判。
顶层 await 为什么会卡住导入它的父模块?因为 ESM 要保证父模块执行时依赖已经执行完,异步依赖没结束,父模块就不能跑。讲清阻塞的范围只限依赖链上的祖先模块,以及改成导出 Promise、动态 import() 两种规避方式。
ESM 循环依赖中,在顶层读取对方还没初始化的 let 导出,会抛 ReferenceError: Cannot access before initialization。讲清暂时性死区为什么延伸到导入方,和 var、函数声明的差别,以及把读取挪进函数、拆出第三个模块的修法。
动态 import() 返回一个 Promise,resolve 后得到模块命名空间对象。它必须异步因为模块可能需要网络加载和执行。附 await 写法、错误处理、缓存机制和与静态 import 的本质区别。
浏览器通过 script type="module" 加载 ES 模块,默认 defer 执行,自动严格模式,跨域需要 CORS。附模块脚本和普通脚本的行为差异、nomodule 降级、常见踩坑场景。
模块顶层 await 会暂停本模块后续求值,并让依赖它的模块等待结果;依赖图中无关的分支仍可继续执行。配置加载等初始化场景适合它,但等待链过长会延迟应用启动,拒绝结果也会沿依赖关系传播。
void 会先求值右侧表达式,再把整个表达式的结果固定为 undefined。void 0 曾被用来取得可靠的 undefined,如今也常用来显式丢弃回调返回值或标记不等待的 Promise;它不会吞掉 Promise 的未处理拒绝。
如何判断磁盘 IO 是系统瓶颈:iostat 哪些列最关键? 本文先给出可直接复述的结论,再用最小代码说明linux iostat util await 磁盘IO瓶颈的工作方式、判断依据、项目边界和常见误区。
NestJS 中如何注册依赖异步初始化(如数据库连接)的 Provider? 本文先给出可直接复述的结论,再用最小代码说明NestJS 异步 Provider useFactory async的工作方式、判断依据、项目边界和常见误区。
NestJS 守卫的 canActivate 如何通过 ExecutionContext 判断放行? 本文先给出可直接复述的结论,再用最小代码说明NestJS Guard canActivate ExecutionContext的工作方式、判断依据、项目边界和常见误区。
在 Next.js 中,多个异步数据获取的执行顺序是否可控?如何确保依赖关系? 本文先给出可直接复述的结论,再用最小代码说明Next.js 异步数据 依赖顺序控制的工作方式、判断依据、项目边界和常见误区。
针对Next.js中多API源并行获取,讲解先发起请求再Promise.all的机制,通过Suspense流式降级,并用具体场景对比串行与并行,强调失败隔离和依赖控制。
在高并发场景下,同一请求中多个异步数据获取是否存在竞态条件?如何防止? 本文先给出可直接复述的结论,再用最小代码说明Next.js 异步数据 竞态条件防护的工作方式、判断依据、项目边界和常见误区。
Next.js 服务器组件中的异步数据获取是否会影响整个页面的渲染性能? 本文先给出可直接复述的结论,再用最小代码说明Next.js 服务器组件 异步数据性能的工作方式、判断依据、项目边界和常见误区。
在 Next.js App Router 中,服务端如何读取数据库或 API 数据?与客户端读取有何本质区别? 本文先给出可直接复述的结论,再用最小代码说明Next.js 服务端读取 数据库 API的工作方式、判断依据、项目边界和常见误区。
在流式渲染中,如何确保数据在组件流式输出时也能正确加载并注入? 本文先给出可直接复述的结论,再用最小代码说明Next.js 流式渲染 数据注入的工作方式、判断依据、项目边界和常见误区。
服务端组件中如何处理异步操作?何时会导致页面阻塞? 本文先给出可直接复述的结论,再用最小代码说明Next.js 服务端组件 异步操作 页面阻塞触发条件的工作方式、判断依据、项目边界和常见误区。
服务端组件中抛出错误时,如何避免整个页面崩溃?应如何处理? 本文先给出可直接复述的结论,再用最小代码说明Next.js 服务端组件 错误边界 防止页面崩溃机制的工作方式、判断依据、项目边界和常见误区。