凌晨两点,你走进一家24小时营业的深夜食堂。老板只有一个人,却要同时应付三桌客人——一桌在等拉面,一桌在等煎饺,还有一桌刚坐下还没点菜。如果是“同步食堂”,老板会先做完拉面再做煎饺,第三桌客人只能干等;但这家店是“异步食堂”——老板记下每桌的需求,先做耗时最短的菜,谁好了先端给谁,三桌客人都不用干等,只是需要“等通知”。 这就是JavaScript异步编程的真实写照。我们的程序就像这位老板,同一时间只能做一件事,但面对网络请求、定时任务、文件读取这些“慢动作”,如果傻傻地排队执行,页面就会卡死。于是,聪明的程序员发明了Promise——一张“订餐小票”,告诉你“你的菜正在做,做好了我叫你”。而async/await,则是让小票变得更像“坐下来等上菜”的体验,让异步代码读起来像同步代码一样流畅自然。
掌握Promise和async/await,是前端工程师从“能写页面”迈向“能写复杂交互应用”的分水岭。它决定了你能否优雅地处理接口请求、数据加载、并发任务这些日常开发中最核心的挑战。
目录
可视化演示
展示程序查询、中断驱动和DMA三种I/O控制方式的数据传输路径与CPU利用率差异,直观对比不同I/O控制策略的性能特征。
本节配有交互动效(进入视口后自动加载,约 3KB)。
程序查询方式(Programmed I/O) CPU不断轮询设备状态 CPU忙等,利用率极低 while(!ready){poll()} 数据经CPU中转至内存
同步与异步:深夜食堂的两种经营模式
深夜食堂对面,还有一家“老王面馆”。老王是个固执的人,他坚持“同步经营”——来了第一位客人,先做他的面,做好了端上桌,再接待第二位。客人少的时候没什么,可一到夜宵高峰,队伍排到街角。最气人的是,排在后面的客人明明只想要一碗白粥,也得等前面三碗拉面全做完。这就是同步阻塞:一个任务不完成,后面的任务全部排队等待。我们的浏览器主线程也是这样——它要负责渲染页面、响应用户点击、执行JavaScript。如果某个任务耗时三秒,这期间用户点哪里都没反应,页面就像“冻住”了。
深夜食堂的老板则聪明得多。他采用“异步经营”:客人来了先记下需求、发一张小票,然后该干嘛干嘛。做面的时间用来煮汤,煮汤的间隙还能先煎个饺子。客人不用死等,可以刷手机,等菜好了小票会响。这就是异步非阻塞:耗时任务交出去,主线程继续服务其他请求。JavaScript是单线程语言,但借助事件循环(Event Loop),它能像这位老板一样“一心多用”。事件循环就像一个传菜员,它不断查看“任务队列”里有没有完成的任务,有就把对应的回调函数拿回主线程执行。
1. 同步模型:代码从上到下逐行执行,前一行没走完,后一行永远等在那里。好比客人不吃完,老板不接待下一位。
2. 异步模型:耗时操作交给浏览器API处理,主线程继续执行后续代码。好比老板记下需求,转身做别的菜。
3. 事件循环:主线程执行完当前所有同步代码后,从任务队列取出一个回调执行。传菜员只在小票响了之后才行动。
回到深夜食堂的比喻:同步的“老王面馆”在高峰期注定要赶走客人,而异步的“深夜食堂”能同时服务十桌客人。前端的核心体验——页面流畅不卡顿——本质上就是异步思想的胜利。理解同步和异步的区别,是你拿到“订餐小票”前的第一课。
// 同步阻塞:第一个计时器没走完,第二个根本没机会开始console.log('第一份面开始做');var start = Date.now();while (Date.now() - start < 1000) {}console.log('第一份面好了'); // 异步非阻塞:先记下需求,时间到了回调自动触发console.log('第一份面开始做');setTimeout(function() { console.log('第一份面好了');}, 1000);console.log('可以接待第二位客人了');
Promise三状态:一张小票的三种命运
深夜食堂的小票,在不同时刻有着完全不同的状态。你刚点完菜时,小票上写着“备餐中”——这是待定状态(pending)。过一会儿,菜做好了端上桌,小票变成“已完成”——这是成功状态(fulfilled)。但如果你点的菜恰好今天卖完了,老板会过来道歉,小票变成“已取消”——这是失败状态(rejected)。一张小票,三种命运,而且一旦从“备餐中”变成“已完成”或“已取消”,就再也不会变回去。菜都端上桌了,不可能重新变回“备餐中”。
Promise对象就是这张小票。它用 new Promise(executor) 创建,executor 是“老板的记事本”,里面写着你点的菜要怎么做。executor 接收两个函数:resolve(菜做好了)和 reject(菜做砸了)。一旦调用 resolve,Promise 就从 pending 变为 fulfilled;调用 reject,就从 pending 变为 rejected。这两条路都是单行道,不可逆转。这个“状态不可逆”特性至关重要——它保证了同一个操作的结果是确定性的,不会出现“先成功又失败”的诡异局面。
1. 创建Promise:new Promise(executor) 创建一张小票,executor 立即执行,好比老板收到订单马上开工。
2. pending 状态:executor 还没调用 resolve 或 reject 时,小票停留在“备餐中”。
3. resolve 触发 fulfilled:调用 resolve(value) 后,小票变成“已完成”,value 是端上桌的菜。
4. reject 触发 rejected:调用 reject(reason) 后,小票变成“已取消”,reason 是取消原因。
5. 状态不可逆:一旦 fulfilled 或 rejected,状态永久锁定,再调用 resolve/reject 无效。
这张小票最妙的地方在于:你不需要一直盯着后厨看。拿到小票后,你可以继续做自己的事。当状态变化时,Promise 会自动通知“关注这张小票的人”。怎么关注?就是下一节要讲的 .then() 方法——相当于在小票上写了“做好后打电话通知我”。小票是媒介,真正的价值在于它让“等待”变得可管理、可组合。
var myOrder = new Promise(function(resolve, reject) { // 模拟做菜过程:2秒后决定命运 setTimeout(function() { var dish = '拉面'; var success = true; if (success) { resolve(dish); // 菜做好了,小票变成 fulfilled } else { reject('今天卖完了'); // 菜做砸了,小票变成 rejected } }, 2000);}); console.log(myOrder); // 立即输出:Promise { <pending> }// 2秒后,小票状态变成 fulfilled 或 rejected
链式调用:从嵌套地狱到流水线
在Promise出现之前,JavaScript的异步代码长什么样?想象一下,你要点一碗拉面,需要经过“下单→付款→等餐→取餐”四个步骤,每一步都是异步的。用回调函数写,就像俄罗斯套娃一样一层套一层:
这就是著名的回调地狱。代码不断向右缩进,逻辑顺序是“先外后内”,读起来像解谜。更可怕的是,每一层的错误处理都要单独写,漏一个就可能“静默失败”——菜做砸了没人知道。
Promise的链式调用彻底改变了这个局面。它的核心机制是:.then() 方法会返回一个新的Promise,这个新Promise的状态取决于回调函数的返回值。就像流水线一样,每道工序完成后的“半成品”自动传给下一道工序。每一层是扁平的一个点,而不是嵌套的一层壳:
1. then方法:.then(onFulfilled) 注册一个“菜好了通知我”的监听器,它接收小票成功后的值。
2. 返回值包装:.then() 的回调返回的任何值,都会被包成新的Promise,实现接力。
3. 链式扁平化:每步一个 .then() 平铺,替代嵌套回调的层级结构。
4. 返回值透传:如果回调返回的不是Promise而是一个普通值,它会被包装成已完成的Promise,继续传递。
流水线的比喻在这里非常贴切:每一道工序都只关心自己的活——切菜的不管炒菜,炒菜的不管装盘。但每一道工序结束后的“半成品”都自动送到下一道工序。这种关注点分离不仅让代码清晰,还让错误处理变得异常简单——整条流水线只要在末端装一个“Catch机”,任何一道工序出问题,都能被它接住。
// 回调地狱:像套娃一样一层层嵌套,代码横向生长placeOrder(function(order) { pay(order, function(receipt) { waitForDish(receipt, function(dish) { pickUp(dish, function(result) { console.log('终于吃上了', result); }); }); });});
// 链式调用:像流水线一样扁平,每步都返回新PromiseplaceOrder() .then(function(order) { return pay(order); // 工序2:付款,返回新Promise }) .then(function(receipt) { return waitForDish(receipt); // 工序3:等餐 }) .then(function(dish) { return pickUp(dish); // 工序4:取餐 }) .then(function(result) { console.log('终于吃上了', result); });
错误处理:厨房里的消防系统
深夜食堂的厨房里,有一套完整的消防系统。每个灶台旁边有灭火器,天花板上烟雾报警器,后门有安全通道。一旦哪个灶台起火,灭火器会第一时间扑救;如果火势变大,报警器会响彻全楼。厨房不可能因为怕起火就不做饭——关键是要有一套“兜底”机制。Promise的错误处理设计,就是这套消防系统。
在同步代码中,你可以用 try/catch 接住任何异常。但在Promise出现之前,异步代码的错误处理是噩梦——每个回调函数里都要写 if (error) 的判断,漏一个就“静默失败”。Promise用 .catch() 方法统一接管错误。更妙的是,异常穿透机制:Promise链上任何一环出错(throw或reject),错误会自动跳过中间的 .then(),直达最近的 .catch()。就像烟雾报警器一样,不管火源在哪一层,警报都会传到消防中心。
1. catch方法:.catch(onRejected) 是 .then(null, onRejected) 的语法糖,专门接住失败。
2. 异常穿透:链上任意一环抛出异常或reject,后续的 .then() 全部跳过,直达 .catch()。
3. throw与reject等价:在 .then() 回调中 throw new Error(),相当于返回一个rejected的Promise。
4. finally清理:无论成功还是失败,.finally() 里的代码一定会执行,适合做清理工作。
消防系统的设计哲学是:不要假设不会着火,而要确保火了能灭。同样的,优秀的异步代码不是“不会出错”,而是“出错后能被优雅地接住”。在实际项目中,.catch() 通常用来展示错误提示、记录日志、恢复默认状态;.finally() 用来关闭loading动画、释放资源。这套体系让异常不再“静默”——每一个错误都变成可见的、可处理的事件。
// 错误处理的完整体系:try/catch + catch + finallyplaceOrder() // 第一步:下单 .then(pay) // 第二步:付款(可能失败) .then(waitForDish) // 第三步:等餐 .then(pickUp) // 第四步:取餐 .catch(function(error) { console.log('订单流程出错了:', error); // 任何一步出错都到这 }) .finally(function() { console.log('无论成败,收拾桌面'); // 清理资源、关loading });
async/await原理:让小票看起来像现场吃
用Promise链式调用,代码已经从“套娃”变成了“流水线”,但终究还是有点“弯弯绕”。能不能让异步代码长得像同步代码一样直白?比如这样:
这就是 async/await——JavaScript异步编程的终极形态。它的本质是语法糖:async 关键字声明一个函数“内部会有异步操作”,await 关键字表示“在这里暂停,等Promise出结果再继续”。看起来像同步,实际上底层依然是Promise和回调。你拿到的小票还在,只是你不用一直盯着它看了——你坐下来,服务员会在菜好的时候端上来,你只需要“假装”在等就好。
async/await 的底层实现离不开生成器(Generator)。生成器函数用 function* 声明,支持在任意位置“暂停”和“恢复”。async/await 本质上就是把生成器包装起来:await 后面的表达式被编译成“暂停点”,Promise完成后自动恢复执行。这个机制,就像传菜员在你面前放了一个“暂停”按钮——菜没到,函数就停在那里;菜一到,按钮自动弹起,函数继续往下走。
1. async函数:用 async 声明的函数,返回值自动被包装成Promise。
2. await操作符:await 可以理解为“暂停并等待”,它后面跟一个表达式,通常是一个Promise。
3. 错误捕获:await 等到的如果是rejected Promise,会直接抛出异常,可用 try/catch 接住。
4. 生成器原理:function* 生成器通过 yield 暂停,通过 next() 恢复,async/await 是其自动化的封装。
深夜食堂的比喻在这里到了高潮:以前你要拿小票、听广播、自己注意叫号;现在你直接坐下来,服务员帮你盯着所有进度,菜到了自然端到你面前。你的代码像同步一样“顺滑”,但底层依然是异步的——你没阻塞任何其他客人。这就是 async/await 的魅力:给程序员同步的心智模型,给系统异步的高效利用。
// 同步风格的异步代码:看起来像在排队,实际上是异步async function eatNoodles() { var order = await placeOrder(); // 同步式:下单 var receipt = await pay(order); // 同步式:付款 var dish = await waitForDish(receipt); // 同步式:等餐 var result = await pickUp(dish); // 同步式:取餐 return result;}
// async/await 的底层简化原理:生成器 + 自动执行器function* gen() { var order = yield placeOrder(); // 暂停点1:等下单结果 var receipt = yield pay(order); // 暂停点2:等付款结果 var dish = yield waitForDish(receipt); // 暂停点3:等餐 return dish;} function autoRun(genFn) { var it = genFn(); // 生成器对象 function next(val) { var r = it.next(val); // 恢复执行,传入上一个结果 if (r.done) return r.value; // 生成器走完了 // r.value 是一个Promise,等它好了再恢复执行 r.value.then(next); } next();} autoRun(gen); // 像 async/await 一样自动跑完整个流程
并发控制:一张桌子的团队协作
深夜食堂来了六位客人,他们是一起的,想在同一桌吃饭。但每个人点的菜耗时不同——拉面三分钟,煎饺两分钟,白粥三十秒。如果按顺序一个一个做,总共要五分钟;如果三口锅同时开火,三分钟就能全部上齐。这就是并发(Concurrency)的价值:不是同时做一件事,而是多个任务交叉推进,让整体耗时取决于最长的那道菜,而非所有菜的总和。
Promise.all 就是“同时开三锅”的利器。它接收一个Promise数组,等所有Promise都变为fulfilled后,返回一个包含所有结果的新Promise。任何一个失败,整个 Promise.all 立即失败。对应到食堂:六位客人的菜全部上齐才算一起开饭,有一道菜做砸了,整桌都吃不成。这种“全有或全无”的语义,非常适合“先并行请求多个接口,再统一渲染页面”的场景。
1. Promise.all:并发执行多个Promise,全部成功才返回结果数组,一个失败则整体失败。
2. Promise.race:并发执行多个Promise,只看第一个落定(成功或失败)的结果,其余忽略。
3. Promise.allSettled:并发执行多个Promise,等全部落定后,返回每个Promise的结果对象(成功或失败都记录)。
4. 性能优化:并行请求比串行请求总耗时更短——总耗时 = 最慢任务耗时,而非任务耗时之和。
Promise.all 和 Promise.race 就像厨房里的两种协作模式:前者是“家庭聚餐,人齐了才动筷”;后者是“比赛吃面,第一碗上来了先吃”。而 allSettled 更像“外卖订单记录”——不管每个订单成功还是失败,都要有一个最终记录归档,方便复盘。在实际开发中,它们分别对应“多接口数据聚合”“超时控制”“部分失败容错”三大经典场景。理解并发控制,你的异步代码才能从“能跑”进化到“快跑”。
// 并发请求:三个接口同时发起,全部完成后再统一处理async function loadDashboard() { var [userInfo, orderList, statistics] = await Promise.all([ fetch('/api/user/info'), // 请求1:用户信息 fetch('/api/user/orders'), // 请求2:订单列表 fetch('/api/user/statistics') // 请求3:数据统计 ]); return { userInfo, orderList, statistics };}
AI 应用场景
场景一:AI聊天机器人的对话流处理
阶段:前端交互层 / 异步请求管理
解决的问题:AI聊天机器人需要同时管理多条对话流——用户发了消息要等AI回复(流式输出)、历史记录要从服务器拉取、用户可能随时中断对话。传统回调方式会让代码陷入“回调地狱”,每条消息的发送、接收、状态更新都嵌套在一起,调试极其困难。用Promise和async/await,对话流程变成同步式的“发出消息→等待回复→更新界面”,逻辑一目了然。
具体案例:某AI客服系统在聊天界面中,用 async/await 管理消息收发:await sendMessage() 发送用户消息并显示loading动画,await getAIResponse() 接收AI流式回复(内部用Promise逐段推送文本),Promise.all 并行加载用户历史会话和AI知识库文档。最终代码像读故事一样清晰,新增功能只需在流程中插入一个 await 步骤。
场景二:AI模型训练的状态监控面板
阶段:训练运维 / 前置数据处理
解决的问题:训练深度学习模型时,需要周期性拉取训练指标(loss、accuracy)、上传日志、检查GPU资源。用同步方式轮询,界面会卡死;用嵌套回调,代码难以维护。Promise的并发控制让“同时监控多项指标”变得简单—— Promise.all 一次拉取所有指标, Promise.race 实现“超时自动重试”,避免某个请求挂起影响整个监控面板。
具体案例:某AI企业的训练监控平台,前端用 async/await 每5秒刷新一次指标面板:Promise.all(拉取loss曲线 + 拉取GPU利用率 + 拉取日志摘要),三者任一失败都会触发 .catch() 显示“连接异常”并自动重试。并发三路请求的总耗时从串行的3秒降为1秒,监控面板做到了真正的“实时”。
Python 演示
"""Promise 核心机制的 Python 教学模拟:用生成器和回调模拟 JavaScript Promise 的状态流转"""import timeimport threadingfrom collections import deque class SimplePromise: """模拟 JavaScript Promise:三种状态 + then 链式调用""" def __init__(self, executor): self._state = "pending" # pending / fulfilled / rejected self._value = None self._handlers = deque() try: executor(self._resolve, self._reject) except Exception as e: self._reject(e) def _resolve(self, value): self._state = "fulfilled" self._value = value self._flush() def _reject(self, reason): self._state = "rejected" self._value = reason self._flush() def _flush(self): while self._handlers: cb = self._handlers.popleft() self._dispatch(cb) def _dispatch(self, cb): try: result = cb(self._value) except Exception as e: self._reject(e) def then(self, on_fulfilled): next_promise = SimplePromise(lambda res, rej: None) if self._state == "pending": self._handlers.append(on_fulfilled) else: self._dispatch(on_fulfilled) return next_promise def catch(self, on_rejected): return self.then(on_rejected) def async_task(name, delay): """模拟一个异步任务:延迟后返回结果""" def executor(resolve, reject): def runner(): time.sleep(delay) resolve(f"{name} 完成") threading.Thread(target=runner).start() return SimplePromise(executor) if __name__ == "__main__": print("=== Promise 状态流转模拟 ===") p = async_task("拉面", 0.5) def on_done(value): print("收到:", value) return "下一道工序" p.then(on_done) print("主线程不阻塞,先做其他事...") time.sleep(1)
SimplePromise 类,直观展示了JavaScript Promise的核心机制——三种状态流转、回调注册、异步触发。它证明了Promise不是JS的专利,而是一种可独立实现的编程模式。self._state = "pending"Promise 初始状态,对应“菜还没做好”的小票executor(self._resolve, self._reject)构造函数立即执行“老板的记事本”,传入两个决定命运的函数self._state = "fulfilled"` / `"rejected"状态一旦切换就不可逆,验证了“小票命运单行道”的特性self._handlers.append(on_fulfilled)pending 状态下的回调先存入队列,等状态定了再执行threading.Thread(target=runner).start()用子线程模拟异步操作,主线程不阻塞p.then(on_done)注册“菜好了通知我”的监听器,返回的next_promise支持继续链式调用
常见误区
误区以为用了Promise,代码就变成异步的了,同步代码“包一层Promise”就能不阻塞。
正解Promise不是一个“耗时操作执行器”,它只是把异步结果的“传递方式”从回调改成了链式。Promise里的executor是同步执行的,真正的异步来自 setTimeout、fetch 这些底层API。就像小票本身不会帮你做菜,做菜的是后厨——小票只是通知你“菜好了”。如果把同步计算包进Promise,它照样会阻塞主线程。
为何常见:因为Promise教程总在讲“解决异步”,初学者容易把“工具”误认为“能力”,就像以为有了筷子就能吃饱,忘了饭是锅做的。
误区以为 `await` 跟同步代码一样,会让整个程序停下来等这一行执行完。
正解await 只暂停当前 async 函数,主线程和其他任务完全不受影响。深夜食堂里你坐在那等拉面,不等于其他客人不能点煎饺——只有你这桌“暂停”,整个食堂照常运转。验证方法很简单:在一个 async 函数里 await 一个耗时任务,同时在外部执行另一段代码,外部代码会立即执行。
为何常见:因为 await 写起来像 “等待”,直觉上认为“等=阻塞”。实际上它是“挂起当前函数,注册回调,让出主线程”。
误区以为 `Promise.all` 中某个Promise失败后,其他正在执行的Promise会被“取消”。
正解Promise.all 只是“快速失败”——只要有一个rejected,整体的结果是rejected。但其他Promise仍然继续执行,不会因为你不关心它们的结果就自动停掉。就像一桌菜里有道菜做砸了,整桌取消,但其他菜还在后厨做着。这可能导致“浪费的请求”或“意外的副作用”,需要用 AbortController 才能真正取消请求。
为何常见:因为“整体失败了”的感觉,让人误以为内部任务都被“一键撤销”。实际Promise一旦创建就无法从外部取消。
误区以为 `async function` 里 `return 5`,外面拿到的是 `5` 这个数字。
正解async 函数永远返回一个Promise。即使你 return 5,外面拿到的也是 Promise.resolve(5),必须用 .then() 或 await 才能拿到真正的5。食堂里你跟服务员说“我要一份面”,你拿到的是小票不是面——小票最终会换成面,但你必须先接过小票。这是async/await新手最容易踩的坑。
为何常见:因为 return 在同步函数里返回“值”太深入人心,忽略了 async 包装器这层“外壳”。
误区以为学了 `async/await` 就可以彻底忘掉Promise,所有异步都用 `async/await` 写。
正解async/await 是Promise之上的语法糖,底层依然是Promise。它无法替代 Promise.all(并发聚合)、Promise.race(超时控制)、Promise.allSettled(部分失败容错)这些高级组合工具。深夜食堂里你可以坐下来等上菜(async/await),但“一桌菜齐了才开饭”(all)或“谁先到先吃”(race)这种协作逻辑,还是得靠小票(Promise)本身的API。
为何常见:因为新手教程总强调async/await“解决回调地狱”,给人“更高级=完全替代”的印象,低估了Promise作为“组合子”的底层价值。
面试题
Q1. Promise 的三种状态不包括以下哪一项?
- A.pending(待定)
- B.fulfilled(已完成)
- C.rejected(已失败)
- D.cancelled(已取消)← 正确答案
答案:D。Promise只有pending、fulfilled、rejected三种状态,没有“cancelled“状态。一旦从pending变为fulfilled或rejected,状态就永久锁定,不存在”取消“这一说。就像深夜食堂的小票,只有”备餐中”“菜好了”“做砸了”三种命运,没有“退单”选项——想退单得重新走流程。
Q2. new Promise(executor) 中的 executor 函数是何时执行的?
- A.调用
.then()时 - B.调用
.catch()时 - C.Promise 创建时同步执行← 正确答案
- D.下一个事件循环周期
答案:C。executor 在 new Promise() 创建时同步立即执行,就像老板收到订单马上开工。resolve 和 reject 两个参数函数在 executor 内部调用,决定Promise的最终状态。很多人误以为executor是异步的,实际上Promise本身不执行异步操作,它只是“承接”异步结果。
Q3. 以下哪个方法可以将多个Promise并发执行,且只要有一个失败就整体失败?
- A.Promise.all← 正确答案
- B.Promise.race
- C.Promise.allSettled
- D.Promise.finally
答案:A。Promise.all 是“全有或全无”——所有Promise都成功才返回结果数组,任何一个rejected就整体rejected。对应比喻:一桌菜要全部上齐才开饭,有一道做砸了整桌取消。race 是只看第一个落定的,allSettled 是无论成败都汇总,finally 只是清理回调。
Q4. 关于 async 函数的返回值,下列说法正确的是?
- A.返回普通值,直接使用
- B.返回Promise,需要then或await取值← 正确答案
- C.返回undefined,不返回任何东西
- D.返回生成器对象
答案:B。async 函数永远返回一个Promise。即使内部 return 5,外部拿到的也是 Promise.resolve(5)。这就像你跟服务员点餐,拿到的是小票而不是面本身——小票最终能换面,但你要先“处理小票”(await或then)。这是async/await新手最容易踩的坑。
Q5. 以下关于 await 的说法,哪个是错误的?
- A.await 只暂停当前 async 函数
- B.await 表达式后面通常跟一个Promise
- C.await 会阻塞整个主线程← 正确答案
- D.await 到 rejected Promise 会抛出异常
答案:C。await 只暂停当前async函数,主线程和宿主环境不受影响。它不会像同步代码那样阻塞整个程序——夜间食堂里你等拉面,不影响别的客人点煎饺。如果用 await 等一个rejected Promise,它会像 throw 一样抛出异常,需要 try/catch 接住。
Q6. 下列哪些方法可以用来处理Promise的错误?
- A..catch(onRejected)← 正确答案
- B..finally(callback)← 正确答案
- C.try/catch包裹await← 正确答案
- D.throw 关键字
答案:ABC。A 是Promise链上接住错误的专用方法;B 的 finally 无论成功失败都会执行,常用于清理资源;C 在 async 函数里用 try/catch 包 await 是推荐做法,因为 await 到rejected会throw。D 的 throw 是制造错误而不是处理错误,所以不选。
Q7. 关于 Promise 链式调用,以下说法正确的是?
- A..then() 返回一个新的Promise← 正确答案
- B..then() 回调返回普通值会包装成Promise← 正确答案
- C.异常会跳过中间的 .then() 直达最近的 .catch()← 正确答案
- D.Promise 链上的状态不可逆
答案:ABC。A 对:每个 .then() 返回新Promise,这是链式的基础;B 对:普通返回值会被 Promise.resolve() 包装,实现数据透传;C 对:异常穿透机制让错误直达最近的 .catch();D 说的“状态不可逆”本身正确,但不是链式调用的特性,答非所问,不选。
Q8. 下列哪个方法适合实现“请求超时自动失败”的功能?
- A.Promise.all
- B.Promise.race← 正确答案
- C.Promise.allSettled
- D.async/await
答案:B。Promise.race 接收多个Promise,返回第一个落定的结果。用它把“真实请求”和“延时3秒的reject”赛跑,谁先到就听谁的——如果请求3秒内没完成,延迟的reject会先触发,实现超时控制。这就像两桌客人抢同一道菜,谁先到谁先吃。all 要全等,allSettled 要全记录,都不适合“抢跑”。
Q9. 关于 Promise.allSettled 和 Promise.all 的区别,说法正确的是?
- A.allSettled 不会因为某个Promise失败而整体失败← 正确答案
- B.all 是“全有或全无”,allSettled 是“记录每一种结果”← 正确答案
- C.allSettled 的结果数组中包含每个Promise的状态← 正确答案
- D.all 的性能一定比 allSettled 好
答案:ABC。A 对:allSettled 等所有Promise落定(无论成败)后返回;B 对:all 一个失败全盘皆输,allSettled 则每个结果都记录;C 对:allSettled 的结果是 [ {status, value/reason}, ...] 格式,包含每个Promise的最终状态和值。D 错误:两者性能没有必然优劣,取决于具体场景。
Q10. 下列哪个是“回调地狱”的主要特征?
- A.代码无限循环执行
- B.异步回调层层嵌套,代码横向生长← 正确答案
- C.多个Promise并发执行
- D.使用事件循环处理任务
答案:B。回调地狱的核心症状是嵌套——每个异步操作的回调里再套下一个异步操作,代码不断向右缩进,可读性和可维护性极差。就像俄罗斯套娃,打开一层又是一层。它不涉及循环、并发或事件循环,而是回调函数“纵向嵌套”带来的结构性混乱。Promise链式调用用“向下生长”替代“向右生长”,正是为了解决这个问题。