前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 自检100题
  • Promise面试题

event loop它的执行顺序:

  • 一开始整个脚本作为一个宏任务执行
  • 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  • 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
  • 执行浏览器UI线程的渲染工作
  • 检查是否有Web Worker任务,有则执行
  • 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

微任务包括:MutationObserver、Promise.then()或reject()、Promise为基础开发的其它技术,比如fetch API、V8的垃圾回收过程、Node独有的process.nextTick。

宏任务包括:script、script 、setTimeout、setInterval 、setImmediate 、I/O 、UI rendering

# 1 Promise的几道基础题

# 1.1 题目一

30 秒速记

  • new Promise(executor) 会立即调用执行器
  • 顺序:promise1 → 1 与期约对象
  • 没调用 resolve、reject,状态保持 pending
  • 同步抛错也会拒绝,不能只查显式 reject
  • 控制台展开对象可能展示稍后的状态

这段代码先打印 promise1,再打印 1 和一个待定的期约对象。 Promise 构造器里的函数会同步执行,不会因为写在期约里就排到后面。执行器既没调用成功或失败回调,也没抛出异常,所以这个期约一直待定。真正延后执行的是通过 then 等方法注册的回调;创建期约本身仍是普通的同步调用。

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
})
console.log('1', promise1);
@前端进阶之旅: 代码已经复制到剪贴板
  • 从上至下,先遇到new Promise,执行该构造函数中的代码promise1
  • 然后执行同步代码1,此时promise1没有被resolve或者reject,因此状态还是pending
'promise1'
'1' Promise{<pending>}
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1执行器里没有定时器,为什么期约还是待定?
参考回答

待定只表示还没有得到成功或失败结果,与有没有异步操作无关。执行器执行结束不会自动完成期约。

追问 2执行器直接 return 123,状态会成功吗?
参考回答

不会,构造器忽略执行器的普通返回值。要通过 resolve(123) 交付结果。

追问 3把第一行日志后面换成 throw new Error() 呢?
参考回答

执行器中的同步异常会让期约拒绝。外层构造调用通常返回拒绝的期约,需要通过拒绝处理函数接住。

追问 4控制台展开后看到成功,能证明打印时已经成功吗?
参考回答

不一定,开发工具可能延迟读取对象内容。分析顺序时根据代码状态变化判断,必要时记录独立的标量日志。

# 1.2 题目二

30 秒速记

  • 输出:1 → 2 → 4 → 3
  • resolve('success') 使期约成功,不终止执行器
  • then 回调走微任务,当前同步代码先完成
  • return resolve(...) 才能顺便退出当前函数
  • 区分“状态改变”和“回调执行”的时机

输出顺序是 1、2、4、3,因为完成期约不会打断正在执行的同步代码。 执行器先打印 1,调用 resolve 后继续打印 2;注册的 then 回调不会当场运行,所以外面的 4 先出现。等当前同步任务结束,微任务才打印 3。这里最容易混淆的是期约已经成功,但成功回调仍然要等待执行机会。

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
@前端进阶之旅: 代码已经复制到剪贴板
  • 从上至下,先遇到new Promise,执行其中的同步代码1
  • 再遇到resolve(‘success’), 将promise的状态改为了resolved并且将值保存下来
  • 继续执行同步代码2
  • 跳出promise,往下执行,碰到promise.then这个微任务,将其加入微任务队列
  • 执行同步代码4
  • 本轮宏任务全部执行完毕,检查微任务队列,发现promise.then这个微任务且状态为resolved,执行它。
1 2 4 3
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1调用了 resolve,后面的 console.log(2) 为什么还执行?
参考回答

resolve 是函数调用,不是控制流程语句。需要停止后续执行时写 return resolve('success')。

追问 2在已经成功的期约上调用 then,能同步得到回调结果吗?
参考回答

不能,回调仍会异步执行。想使用结果,就继续放在回调里,或在异步函数中用 await。

追问 3把外面的 4 放到一个零延迟定时器里呢?
参考回答

在普通浏览器执行场景下,成功回调的微任务会先执行,再进入定时器任务。前面的同步 1、2 仍然最先输出。

追问 4resolve 后再调用 reject,会打印哪个回调?
参考回答

第一次有效确定结果后,后续的完成调用不会改变它。这里仍走成功分支,不会因为后来调用 reject 改成失败。

# 1.3 题目三

30 秒速记

  • 输出:1 → 2 → 4,没有 3
  • 注册成功回调不会主动让期约成功
  • 待定时保存反应,完成后才安排对应回调
  • 等再久也不会自动从 pending 变成成功
  • 超时与取消需要单独设计

这里不会打印 3,因为期约一直没有被完成。 执行器中的两条日志同步输出,外层接着打印 4,但成功回调只是在等待结果。微任务机制不会替业务补出一个结果,也不会因为执行器运行完了就自动调用 then。排查类似的请求一直转圈时,要检查所有分支有没有交付结果,而不是只检查回调是否注册成功。

const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
@前端进阶之旅: 代码已经复制到剪贴板
  • 和题目二相似,只不过在promise中并没有resolve或者reject
  • 因此promise.then并不会执行,它只有在被改变了状态之后才会执行。
1 2 4
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1等一分钟,成功回调会自己执行吗?
参考回答

不会,期约没有默认超时。必须有某条路径调用完成回调,或者由外部超时逻辑给调用方一个失败结果。

追问 2给它加一个 catch,能结束等待吗?
参考回答

不能,拒绝处理也只是等待拒绝结果。期约既不成功也不失败时,这两个处理分支都不会运行。

追问 3想让调用方最多等三秒,可以怎么写?
参考回答

可以把任务与一个定时拒绝的期约交给 Promise.race。这只限制等待结果,不会自动终止底层请求或清理资源。

追问 4执行器某个分支漏了 resolve,怎么避免?
参考回答

把成功、失败和提前退出路径逐一列清楚,确保需要返回结果的路径都有完成动作。能直接返回已有异步接口时,就不要再手动包一层构造器。

# 1.4 题目四

30 秒速记

  • 同步输出:promise1,再打印两个期约
  • promise1 已成功,值为 resolve1
  • promise2 此时待定,等待 then 回调执行
  • 随后打印 resolve1,promise2 成功为 undefined
  • then 返回新期约,不是返回原来的对象

第二个期约要等成功回调执行结束,才能确定自己的结果。 第一个期约在执行器里已经成功,所以同步打印它时逻辑状态是成功;调用 then 得到的第二个期约此时还在等待。同步日志完成后,回调打印 resolve1,又因为没有显式返回值,第二个期约最终以 undefined 成功。控制台对象展示可能延后,判断状态要按这条执行链来推。

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
@前端进阶之旅: 代码已经复制到剪贴板
  • 从上至下,先遇到new Promise,执行该构造函数中的代码promise1
  • 碰到resolve函数, 将promise1的状态改变为resolved, 并将结果保存下来
  • 碰到promise1.then这个微任务,将它放入微任务队列
  • promise2是一个新的状态为pending的Promise`
  • 执行同步代码1, 同时打印出promise1的状态是resolved
  • 执行同步代码2,同时打印出promise2的状态是pending
  • 宏任务执行完毕,查找微任务队列,发现promise1.then这个微任务且状态为resolved,执行它。
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 自检100题
  • Promise面试题
    • 1 Promise的几道基础题
      • 1.1 题目一
      • 1.2 题目二
      • 1.3 题目三
      • 1.4 题目四
      • 1.5 题目五
      • 1.6 题目六
    • 2. Promise结合setTimeout
    • 3. Promise中的then、catch、finally
    • 4. Promise中的all和race
    • 5. async/await的几道题
    • 6. async处理错误
    • 7. 综合题
    • 8. 几道大厂的面试题
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

Promise面试题详解与常见考点

event loop它的执行顺序:

  • 一开始整个脚本作为一个宏任务执行
  • 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  • 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
  • 执行浏览器UI线程的渲染工作
  • 检查是否有Web Worker任务,有则执行
  • 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

微任务包括:MutationObserver、Promise.then()或reject()、Promise为基础开发的其它技术,比如fetch API、V8的垃圾回收过程、Node独有的process.nextTick。

宏任务包括:script、script 、setTimeout、setInterval 、setImmediate 、I/O 、UI rendering

# 1 Promise的几道基础题

# 1.1 题目一

30 秒速记

  • new Promise(executor) 会立即调用执行器
  • 顺序:promise1 → 1 与期约对象
  • 没调用 resolve、reject,状态保持 pending
  • 同步抛错也会拒绝,不能只查显式 reject
  • 控制台展开对象可能展示稍后的状态

这段代码先打印 promise1,再打印 1 和一个待定的期约对象。 Promise 构造器里的函数会同步执行,不会因为写在期约里就排到后面。执行器既没调用成功或失败回调,也没抛出异常,所以这个期约一直待定。真正延后执行的是通过 then 等方法注册的回调;创建期约本身仍是普通的同步调用。

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
})
console.log('1', promise1);
@前端进阶之旅: 代码已经复制到剪贴板
  • 从上至下,先遇到new Promise,执行该构造函数中的代码promise1
  • 然后执行同步代码1,此时promise1没有被resolve或者reject,因此状态还是pending
'promise1'
'1' Promise{<pending>}
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1执行器里没有定时器,为什么期约还是待定?
参考回答

待定只表示还没有得到成功或失败结果,与有没有异步操作无关。执行器执行结束不会自动完成期约。

追问 2执行器直接 return 123,状态会成功吗?
参考回答

不会,构造器忽略执行器的普通返回值。要通过 resolve(123) 交付结果。

追问 3把第一行日志后面换成 throw new Error() 呢?
参考回答

执行器中的同步异常会让期约拒绝。外层构造调用通常返回拒绝的期约,需要通过拒绝处理函数接住。

追问 4控制台展开后看到成功,能证明打印时已经成功吗?
参考回答

不一定,开发工具可能延迟读取对象内容。分析顺序时根据代码状态变化判断,必要时记录独立的标量日志。

# 1.2 题目二

30 秒速记

  • 输出:1 → 2 → 4 → 3
  • resolve('success') 使期约成功,不终止执行器
  • then 回调走微任务,当前同步代码先完成
  • return resolve(...) 才能顺便退出当前函数
  • 区分“状态改变”和“回调执行”的时机

输出顺序是 1、2、4、3,因为完成期约不会打断正在执行的同步代码。 执行器先打印 1,调用 resolve 后继续打印 2;注册的 then 回调不会当场运行,所以外面的 4 先出现。等当前同步任务结束,微任务才打印 3。这里最容易混淆的是期约已经成功,但成功回调仍然要等待执行机会。

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
@前端进阶之旅: 代码已经复制到剪贴板
  • 从上至下,先遇到new Promise,执行其中的同步代码1
  • 再遇到resolve(‘success’), 将promise的状态改为了resolved并且将值保存下来
  • 继续执行同步代码2
  • 跳出promise,往下执行,碰到promise.then这个微任务,将其加入微任务队列
  • 执行同步代码4
  • 本轮宏任务全部执行完毕,检查微任务队列,发现promise.then这个微任务且状态为resolved,执行它。
1 2 4 3
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1调用了 resolve,后面的 console.log(2) 为什么还执行?
参考回答

resolve 是函数调用,不是控制流程语句。需要停止后续执行时写 return resolve('success')。

追问 2在已经成功的期约上调用 then,能同步得到回调结果吗?
参考回答

不能,回调仍会异步执行。想使用结果,就继续放在回调里,或在异步函数中用 await。

追问 3把外面的 4 放到一个零延迟定时器里呢?
参考回答

在普通浏览器执行场景下,成功回调的微任务会先执行,再进入定时器任务。前面的同步 1、2 仍然最先输出。

追问 4resolve 后再调用 reject,会打印哪个回调?
参考回答

第一次有效确定结果后,后续的完成调用不会改变它。这里仍走成功分支,不会因为后来调用 reject 改成失败。

# 1.3 题目三

30 秒速记

  • 输出:1 → 2 → 4,没有 3
  • 注册成功回调不会主动让期约成功
  • 待定时保存反应,完成后才安排对应回调
  • 等再久也不会自动从 pending 变成成功
  • 超时与取消需要单独设计

这里不会打印 3,因为期约一直没有被完成。 执行器中的两条日志同步输出,外层接着打印 4,但成功回调只是在等待结果。微任务机制不会替业务补出一个结果,也不会因为执行器运行完了就自动调用 then。排查类似的请求一直转圈时,要检查所有分支有没有交付结果,而不是只检查回调是否注册成功。

const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
@前端进阶之旅: 代码已经复制到剪贴板
  • 和题目二相似,只不过在promise中并没有resolve或者reject
  • 因此promise.then并不会执行,它只有在被改变了状态之后才会执行。
1 2 4
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1等一分钟,成功回调会自己执行吗?
参考回答

不会,期约没有默认超时。必须有某条路径调用完成回调,或者由外部超时逻辑给调用方一个失败结果。

追问 2给它加一个 catch,能结束等待吗?
参考回答

不能,拒绝处理也只是等待拒绝结果。期约既不成功也不失败时,这两个处理分支都不会运行。

追问 3想让调用方最多等三秒,可以怎么写?
参考回答

可以把任务与一个定时拒绝的期约交给 Promise.race。这只限制等待结果,不会自动终止底层请求或清理资源。

追问 4执行器某个分支漏了 resolve,怎么避免?
参考回答

把成功、失败和提前退出路径逐一列清楚,确保需要返回结果的路径都有完成动作。能直接返回已有异步接口时,就不要再手动包一层构造器。

# 1.4 题目四

30 秒速记

  • 同步输出:promise1,再打印两个期约
  • promise1 已成功,值为 resolve1
  • promise2 此时待定,等待 then 回调执行
  • 随后打印 resolve1,promise2 成功为 undefined
  • then 返回新期约,不是返回原来的对象

第二个期约要等成功回调执行结束,才能确定自己的结果。 第一个期约在执行器里已经成功,所以同步打印它时逻辑状态是成功;调用 then 得到的第二个期约此时还在等待。同步日志完成后,回调打印 resolve1,又因为没有显式返回值,第二个期约最终以 undefined 成功。控制台对象展示可能延后,判断状态要按这条执行链来推。

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
@前端进阶之旅: 代码已经复制到剪贴板
  • 从上至下,先遇到new Promise,执行该构造函数中的代码promise1
  • 碰到resolve函数, 将promise1的状态改变为resolved, 并将结果保存下来
  • 碰到promise1.then这个微任务,将它放入微任务队列
  • promise2是一个新的状态为pending的Promise`
  • 执行同步代码1, 同时打印出promise1的状态是resolved
  • 执行同步代码2,同时打印出promise2的状态是pending
  • 宏任务执行完毕,查找微任务队列,发现promise1.then这个微任务且状态为resolved,执行它。
'promise1'
'1' Promise{<resolved>: 'resolve1'}
'2' Promise{<pending>}
'resolve1'
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1promise1 === promise2 会得到什么?
参考回答

是 false,then 创建并返回一个新期约。这个新对象负责承接回调的返回值或异常。

追问 2回调写成 return res + '!',第二个期约是什么结果?
参考回答

它会以 resolve1! 成功。回调正常返回什么,新期约就接收什么;返回期约或可兑现对象时还会采用对应结果。

追问 3第一个期约成功,第二个就一定成功吗?
参考回答

不一定,成功回调也可能抛错或返回拒绝的期约。后续状态取决于回调执行结果。

追问 4不传成功回调,写 promise1.then() 会怎样?
参考回答

成功值会继续传递给新的期约。返回对象仍然是新的,不能用对象相等判断是否传递了结果。

# 1.5 题目五

30 秒速记

  • 输出:1 → start → success
  • 定义 fn 不执行,调用 fn() 才进入构造器
  • 执行器同步打印 1 并完成期约
  • 成功回调进入微任务,晚于外层同步日志
  • 区分函数调用与回调注册,别见期约就全部后移

fn() 是同步调用,所以它里面的 1 会先于外层的 start。 调用过程中创建期约,执行器立即运行并交付 success;随后注册成功回调,当前同步流程继续打印 start。等同步代码完成,回调才打印成功值。做这类顺序题时,把“调用一个返回期约的函数”和“等待期约回调”分开,执行顺序就很清楚。

const fn = () => (new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
}))
fn().then(res => {
  console.log(res)
})
console.log('start')
@前端进阶之旅: 代码已经复制到剪贴板

请仔细看看哦,fn函数它是直接返回了一个new Promise的,而且fn函数的调用是在start之前,所以它里面的内容应该会先执行。

1
'start'
'success'
@前端进阶之旅: 代码已经复制到剪贴板

← 自检100题

fe