【吃透 Promise】从基础到面试高频(手写 + 输出题 + 原理)

文章目录

    • [一、Promise 出现的背景:解决回调地狱](#一、Promise 出现的背景:解决回调地狱)
      • [1. 为什么需要 Promise?](#1. 为什么需要 Promise?)
      • [2. Promise 核心作用](#2. Promise 核心作用)
    • [二、Promise 基础核心知识点(必背)](#二、Promise 基础核心知识点(必背))
      • [1. 三种状态(不可逆)](#1. 三种状态(不可逆))
      • [2. 执行规则:同步 + 微任务](#2. 执行规则:同步 + 微任务)
      • [3. 核心 API 速查](#3. 核心 API 速查)
    • [三、Promise 原理:手写 Promise(核心实现)](#三、Promise 原理:手写 Promise(核心实现))
    • 四、面试必考:经典输出题(逐行解析)
      • [题 0:经典综合题](#题 0:经典综合题)
    • [五、Promise 高频输出题合集(面试必刷)](#五、Promise 高频输出题合集(面试必刷))
      • [题 1:基础链式调用](#题 1:基础链式调用)
      • [题 2:catch 穿透](#题 2:catch 穿透)
      • [题 3:finally 特性](#题 3:finally 特性)
      • [题 4:值穿透(经典陷阱)](#题 4:值穿透(经典陷阱))
      • [题 5:返回 Promise 的 then](#题 5:返回 Promise 的 then)
      • [题 6:微任务嵌套(刁钻)](#题 6:微任务嵌套(刁钻))
      • [题 7:多个微任务交替(高难度)](#题 7:多个微任务交替(高难度))
      • [题 8:async/await 与 Promise 混用(超高频)](#题 8:async/await 与 Promise 混用(超高频))
      • [题 9:Promise.all 与错误处理](#题 9:Promise.all 与错误处理)
      • [题 10:resolve 传入 Promise(超刁钻)](#题 10:resolve 传入 Promise(超刁钻))
      • [题 11:finally 的返回值陷阱](#题 11:finally 的返回值陷阱)
      • [题 12:多层 catch 后的状态](#题 12:多层 catch 后的状态)
      • [题 13:同步抛错 vs 异步抛错](#题 13:同步抛错 vs 异步抛错)
      • [题 14:Promise.race 竞速场景](#题 14:Promise.race 竞速场景)
      • [题 15:地狱级综合题(模拟真实面试)](#题 15:地狱级综合题(模拟真实面试))
    • [六、手撕 Promise 核心 API(面试高频手写)](#六、手撕 Promise 核心 API(面试高频手写))
      • [1. 手撕 Promise.all(重点)](#1. 手撕 Promise.all(重点))
      • [2. 手撕 Promise.race](#2. 手撕 Promise.race)
      • [3. 手撕 Promise.allSettled](#3. 手撕 Promise.allSettled)
      • [4. 手撕 Promise.any](#4. 手撕 Promise.any)
      • [5. 手撕 Promise 并发限制(加分题)](#5. 手撕 Promise 并发限制(加分题))
      • [6. 手撕 Promise 重试机制(加分题)](#6. 手撕 Promise 重试机制(加分题))
    • [七、Promise 高频面试问答(背会直接用)](#七、Promise 高频面试问答(背会直接用))
      • [Q1:Promise 和 async/await 的关系?](#Q1:Promise 和 async/await 的关系?)
      • [Q2:then 为什么可以链式调用?](#Q2:then 为什么可以链式调用?)
      • [Q3:Promise 错误捕获方式有哪些?](#Q3:Promise 错误捕获方式有哪些?)
      • [Q4:all 和 allSettled 的区别?](#Q4:all 和 allSettled 的区别?)
      • [Q5:Promise 的错误为什么不能被 try/catch 捕获?](#Q5:Promise 的错误为什么不能被 try/catch 捕获?)
      • [Q6:如何取消一个 Promise?](#Q6:如何取消一个 Promise?)
      • [Q7:Promise.resolve() 和 new Promise(resolve => resolve()) 有什么区别?](#Q7:Promise.resolve() 和 new Promise(resolve => resolve()) 有什么区别?)
      • Q8:微任务和宏任务的完整执行模型?
    • 八、总结速查表

本文覆盖所有 Promise 面试考点:诞生背景、基础用法、微任务执行机制、经典输出题(含刁钻变体)、手撕核心 API(all / race / allSettled / any)及原理剖析。背完就能搞定 99% 的 Promise 面试。


一、Promise 出现的背景:解决回调地狱

1. 为什么需要 Promise?

早期异步代码用回调函数实现,多层嵌套会形成回调地狱(Callback Hell),代码可读性极差、难以维护:

js 复制代码
// 回调地狱:嵌套多层,逻辑混乱,错误处理更是噩梦
ajax('url1', (res1) => {
  ajax('url2', (res2) => {
    ajax('url3', (res3) => {
      // 如果这里报错,外层 try/catch 无法捕获
      console.log(res3)
    }, err => console.log(err))
  }, err => console.log(err))
}, err => console.log(err))

核心痛点

  • 嵌套层级深,逻辑难以理解
  • 每一层都要单独处理错误
  • 无法优雅地并行 / 串行控制多个异步任务

2. Promise 核心作用

  • 把异步回调嵌套改成链式调用,代码扁平化
  • 统一处理异步成功 / 失败,支持并行 / 串行控制
  • 是 JS 异步编程的基石(async/await 是语法糖,底层还是 Promise)

二、Promise 基础核心知识点(必背)

1. 三种状态(不可逆)

状态 触发条件 是否可逆
pending(等待中) 初始状态 ---
fulfilled(已成功) 调用 resolve() ❌ 永久锁定
rejected(已失败) 调用 reject() ❌ 永久锁定

状态一旦改变,永久锁定,无法再次修改。

js 复制代码
const p = new Promise((resolve, reject) => {
  resolve('first')   // 状态变为 fulfilled
  reject('second')   // 无效!状态已锁定
  resolve('third')   // 无效!状态已锁定
})
p.then(res => console.log(res)) // 只输出 'first'

2. 执行规则:同步 + 微任务

  • Promise 构造函数内的代码是同步执行的
  • .then() / .catch() / .finally()微任务,会进入微任务队列,等同步代码执行完才执行
  • 执行顺序:同步代码 → 微任务 → 宏任务(setTimeout、setInterval、ajax 等)
js 复制代码
console.log('1 - 同步')

new Promise((resolve) => {
  console.log('2 - 构造函数内,同步')
  resolve()
}).then(() => {
  console.log('4 - 微任务')
})

setTimeout(() => {
  console.log('5 - 宏任务')
}, 0)

console.log('3 - 同步')

// 输出:1 2 3 4 5

3. 核心 API 速查

实例方法:

方法 作用
.then(onFulfilled, onRejected) 成功回调(第二参数可选,处理失败)
.catch(onRejected) 失败回调,等价于 .then(null, onRejected)
.finally(callback) 无论成败都执行,不接收参数,不改变状态

静态方法:

方法 作用
Promise.resolve(value) 返回一个成功状态的 Promise
Promise.reject(reason) 返回一个失败状态的 Promise
Promise.all(iterable) 全部成功才成功,有一个失败即失败
Promise.race(iterable) 第一个完成的(无论成败)决定结果
Promise.allSettled(iterable) 全部完成后返回所有结果(含成败状态)
Promise.any(iterable) 第一个成功的决定结果,全部失败才失败

三、Promise 原理:手写 Promise(核心实现)

想真正理解 Promise,必须手写过一次。以下是符合 Promise/A+ 规范的核心实现:

js 复制代码
class MyPromise {
  // 状态常量
  static PENDING = 'pending'
  static FULFILLED = 'fulfilled'
  static REJECTED = 'rejected'

  constructor(executor) {
    this.status = MyPromise.PENDING
    this.value = undefined
    this.reason = undefined
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = (value) => {
      // 只有 pending 状态才能转换
      if (this.status === MyPromise.PENDING) {
        this.status = MyPromise.FULFILLED
        this.value = value
        this.onFulfilledCallbacks.forEach(fn => fn(value))
      }
    }

    const reject = (reason) => {
      if (this.status === MyPromise.PENDING) {
        this.status = MyPromise.REJECTED
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn => fn(reason))
      }
    }

    try {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  then(onFulfilled, onRejected) {
    // 值穿透:若不是函数,默认透传
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }

    // then 返回一个新的 Promise(链式调用的关键)
    return new MyPromise((resolve, reject) => {
      const handleFulfilled = () => {
        queueMicrotask(() => {
          try {
            const result = onFulfilled(this.value)
            resolvePromise(result, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }

      const handleRejected = () => {
        queueMicrotask(() => {
          try {
            const result = onRejected(this.reason)
            resolvePromise(result, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }

      if (this.status === MyPromise.FULFILLED) handleFulfilled()
      else if (this.status === MyPromise.REJECTED) handleRejected()
      else {
        // pending 状态:异步,先收集回调
        this.onFulfilledCallbacks.push(handleFulfilled)
        this.onRejectedCallbacks.push(handleRejected)
      }
    })
  }

  catch(onRejected) {
    return this.then(null, onRejected)
  }

  finally(callback) {
    return this.then(
      value => MyPromise.resolve(callback()).then(() => value),
      reason => MyPromise.resolve(callback()).then(() => { throw reason })
    )
  }

  static resolve(value) {
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
  }

  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason))
  }
}

// 处理 then 返回值(Promise/A+ 2.3 规范)
function resolvePromise(result, resolve, reject) {
  if (result instanceof MyPromise) {
    result.then(resolve, reject)
  } else {
    resolve(result)
  }
}

四、面试必考:经典输出题(逐行解析)

题 0:经典综合题

js 复制代码
console.log(5)
try {
  new Promise((resolve, reject) => {
    console.log(3)     // 构造函数内:同步执行
    reject('err')      // 直接把 Promise 状态改为 rejected
  }).catch(() => {
    console.log(2)     // 微任务1:捕获失败,执行
  }).then(() => {
    console.log(9)     // 微任务2:catch执行完,返回新Promise成功,执行
  })
  console.log(10)     // 同步代码
} catch (e) {
  console.log(11)     // Promise的错误是微任务,不会触发try/catch
}

执行步骤:

  1. 同步执行 console.log(5) → 输出 5
  2. 进入 try,执行 Promise 构造函数(同步)→ console.log(3) → 输出 3
  3. 执行 reject('err'),Promise 状态变为 rejected,注册 .catch() 微任务
  4. 继续执行同步代码 console.log(10) → 输出 10
  5. 同步代码执行完毕,开始执行微任务队列
  6. 执行 .catch()console.log(2) → 输出 2
  7. .catch() 返回一个新的成功状态 Promise → 执行后续 .then()console.log(9) → 输出 9
  8. Promise 错误是微任务,不会触发同步的 try/catch → 不输出 11

最终输出:5 3 10 2 9


五、Promise 高频输出题合集(面试必刷)

题 1:基础链式调用

js 复制代码
Promise.resolve()
  .then(() => { console.log(1) })
  .then(() => { console.log(2) })
console.log(3)

输出:3 1 2

console.log(3) 是同步代码,先执行;.then() 是微任务,同步执行完后依次执行。


题 2:catch 穿透

js 复制代码
new Promise((res, rej) => {
  rej('err')
}).then(() => {
  console.log(1)
}).then(() => {
  console.log(2)
}).catch(() => {
  console.log(3)
})

输出:3

rejected 状态会穿透所有 .then()(没有 onRejected 参数时),直到第一个 .catch()


题 3:finally 特性

js 复制代码
Promise.resolve('ok')
  .finally(() => console.log(1))
  .then(res => console.log(res))

输出:1 ok

finally 不接收参数,不改变 Promise 状态,透传 'ok'


题 4:值穿透(经典陷阱)

js 复制代码
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

输出:1

.then() 的参数如果不是函数,会被忽略,发生值穿透 (透传上一个 Promise 的值)。2Promise.resolve(3) 都不是函数,所以最终透传初始值 1


题 5:返回 Promise 的 then

js 复制代码
Promise.resolve()
  .then(() => {
    return new Promise(resolve => {
      setTimeout(() => resolve(1), 0)
    })
  })
  .then(res => console.log(res))

console.log(2)

输出:2 1

第一个 .then() 返回了一个 Promise,第二个 .then() 会等待这个内部 Promise resolve 后才执行,因此延迟到 setTimeout 之后。


题 6:微任务嵌套(刁钻)

js 复制代码
Promise.resolve().then(() => {
  console.log(1)
  Promise.resolve().then(() => {
    console.log(2)
  })
}).then(() => {
  console.log(3)
})

输出:1 2 3

第一个 .then() 执行后,内部 Promise.resolve().then(()=>console.log(2))2 的微任务加入队列,随后外层第二个 .then(console.log(3)) 也加入队列。微任务队列:[打印2, 打印3],依次执行。


题 7:多个微任务交替(高难度)

js 复制代码
Promise.resolve().then(() => {
  console.log(1)
  return Promise.resolve(5)
}).then(v => {
  console.log(v)
})

Promise.resolve().then(() => {
  console.log(2)
}).then(() => {
  console.log(3)
}).then(() => {
  console.log(4)
})

输出:1 2 3 4 5

这是最容易答错的题目,关键在于 return Promise.resolve(5) 会多消耗 2 个微任务 tick

详细分析(按微任务队列轮次):

  • 第1轮微任务 :执行 then(()=>console.log(1)...),输出 1 ;执行 then(()=>console.log(2)),输出 2
  • 第一个链返回了 Promise.resolve(5),需要额外 2 次 tick 才能将 5 传递给下一个 .then()
  • 第2轮微任务 :第二链执行 then(()=>console.log(3)),输出 3 ;第一链内部处理 Promise.resolve(5)(第1个额外 tick)
  • 第3轮微任务 :第二链执行 then(()=>console.log(4)),输出 4 ;第一链内部处理(第2个额外 tick),将 5 放入队列
  • 第4轮微任务 :第一链执行 .then(v=>console.log(v)),输出 5

题 8:async/await 与 Promise 混用(超高频)

js 复制代码
async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}

async function async2() {
  console.log('async2')
}

console.log('script start')
setTimeout(() => console.log('setTimeout'), 0)
async1()
new Promise(resolve => {
  console.log('promise1')
  resolve()
}).then(() => {
  console.log('promise2')
})
console.log('script end')

输出:

复制代码
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

解析:

  1. 'script start' 同步输出
  2. setTimeout 注册宏任务
  3. 调用 async1():同步输出 'async1 start',调用 async2()
  4. async2() 同步输出 'async2',返回 Promise.resolve(undefined)
  5. await 等待 async2() resolve,'async1 end' 后面的代码注册为微任务
  6. 回到主线程,执行 new Promise 构造函数,同步输出 'promise1'resolve() 后注册 .then() 微任务
  7. 同步输出 'script end'
  8. 执行微任务:'async1 end',然后 'promise2'
  9. 执行宏任务:'setTimeout'

题 9:Promise.all 与错误处理

js 复制代码
const p1 = new Promise((resolve) => setTimeout(() => resolve(1), 100))
const p2 = new Promise((_, reject) => setTimeout(() => reject('err'), 50))
const p3 = new Promise((resolve) => setTimeout(() => resolve(3), 200))

Promise.all([p1, p2, p3])
  .then(res => console.log('all:', res))
  .catch(err => console.log('catch:', err))

输出:catch: err

p2 在 50ms 时失败,Promise.all 立即 reject,不等 p1p3


题 10:resolve 传入 Promise(超刁钻)

js 复制代码
const p1 = new Promise(resolve => {
  resolve(Promise.resolve('hello'))
})
p1.then(res => console.log(res))

const p2 = new Promise(resolve => {
  resolve('world')
})
p2.then(res => console.log(res))

输出:world hello

resolve 接收的是一个 Promise 时,会等待该 Promise 完成,额外消耗 2 个微任务 tick,所以 p2.then 先于 p1.then 执行。


题 11:finally 的返回值陷阱

js 复制代码
Promise.resolve('ok')
  .finally(() => {
    return Promise.reject('error in finally')
  })
  .then(res => console.log('then:', res))
  .catch(err => console.log('catch:', err))

输出:catch: error in finally

finally 内部如果抛出错误或 return 一个 rejected Promise会改变最终状态为 rejected ,这是 finally 唯一能影响状态的情况。


题 12:多层 catch 后的状态

js 复制代码
Promise.reject('err')
  .catch(err => {
    console.log(1, err)
    return 'recovered'
  })
  .catch(err => {
    console.log(2, err)
  })
  .then(res => {
    console.log(3, res)
  })

输出:1 'err'3 'recovered'

第一个 .catch() 处理了错误并 return 'recovered'(正常返回),后续状态变为 fulfilled,第二个 .catch() 不执行,.then() 接收到 'recovered'


题 13:同步抛错 vs 异步抛错

js 复制代码
// 情况A:同步抛错(会被 Promise 捕获)
new Promise((resolve, reject) => {
  throw new Error('sync error')
}).catch(err => console.log('A:', err.message))

// 情况B:异步抛错(无法被 Promise 捕获!)
new Promise((resolve, reject) => {
  setTimeout(() => {
    throw new Error('async error') // ❌ 无法捕获,直接报 unhandledrejection
  }, 0)
}).catch(err => console.log('B:', err.message))

输出:A: sync error(情况B的错误无法被捕获,直接抛到全局)

Promise 只能捕获同步抛出 的错误或通过 reject() 传递的错误,异步回调内的抛错已脱离 Promise 的执行上下文。


题 14:Promise.race 竞速场景

js 复制代码
function timeout(ms) {
  return new Promise((_, reject) => 
    setTimeout(() => reject(new Error('timeout')), ms)
  )
}

Promise.race([
  fetch('/api/data'),          // 假设需要 3000ms
  timeout(2000)
]).then(res => console.log('success'))
  .catch(err => console.log('error:', err.message))

输出:error: timeout(2000ms 超时后)

实际项目中 Promise.race 常用于请求超时控制


题 15:地狱级综合题(模拟真实面试)

js 复制代码
console.log(1)

setTimeout(() => console.log(2), 0)

new Promise((resolve) => {
  console.log(3)
  resolve()
  console.log(4)
}).then(() => {
  console.log(5)
  new Promise(resolve => resolve()).then(() => {
    console.log(6)
  })
}).then(() => {
  console.log(7)
})

new Promise((resolve) => {
  console.log(8)
  resolve()
}).then(() => {
  console.log(9)
}).then(() => {
  console.log(10)
})

console.log(11)

输出:1 3 4 8 11 5 9 6 7 10 2

逐步分析:

  • 同步13(构造函数)、4(resolve 后继续同步)、8(第二个构造函数)、11
  • 微任务第1轮5(第一个Promise的then1)、9(第二个Promise的then1)
    • then1 执行时注册了内部的 Promise.resolve().then(=>6)(微任务)和外层 then2(=>7)(微任务),以及 then2(=>10)
  • 微任务第2轮6(内部 then)、7(外层 then2)、10(第二个Promise的then2)
  • 宏任务2

六、手撕 Promise 核心 API(面试高频手写)

1. 手撕 Promise.all(重点)

作用: 所有 Promise 成功才返回结果数组(顺序与输入一致),有一个失败直接 reject

js 复制代码
Promise.myAll = function (promises) {
  return new Promise((resolve, reject) => {
    const result = []
    let count = 0

    // 空数组直接返回
    if (promises.length === 0) return resolve(result)

    promises.forEach((p, index) => {
      // 包装成 Promise,兼容普通值
      Promise.resolve(p).then(res => {
        result[index] = res  // 用 index 保证顺序,不能用 push
        count++
        if (count === promises.length) resolve(result)
      }).catch(reject)  // 有一个失败直接 reject
    })
  })
}

易错点:result[index] = res 而不是 result.push(res),因为要保证结果顺序与入参一致。


2. 手撕 Promise.race

作用: 返回第一个完成的 Promise(无论成功 / 失败)

js 复制代码
Promise.myRace = function (promises) {
  return new Promise((resolve, reject) => {
    promises.forEach(p => {
      Promise.resolve(p).then(resolve).catch(reject)
    })
  })
}

原理: resolve / reject 只有第一次调用生效(状态锁定),后续调用无效。


3. 手撕 Promise.allSettled

作用: 所有 Promise 完成后(无论成败)返回结果数组,每项包含 status + value/reason

js 复制代码
Promise.myAllSettled = function (promises) {
  return new Promise(resolve => {
    const result = []
    let count = 0

    if (promises.length === 0) return resolve(result)

    promises.forEach((p, index) => {
      Promise.resolve(p)
        .then(res => {
          result[index] = { status: 'fulfilled', value: res }
        })
        .catch(err => {
          result[index] = { status: 'rejected', reason: err }
        })
        .finally(() => {
          count++
          if (count === promises.length) resolve(result)
        })
    })
  })
}

4. 手撕 Promise.any

作用: 返回第一个成功的 Promise,全部失败才报 AggregateError

js 复制代码
Promise.myAny = function (promises) {
  return new Promise((resolve, reject) => {
    let count = 0
    const errors = []

    if (promises.length === 0) {
      return reject(new AggregateError([], 'All promises were rejected'))
    }

    promises.forEach((p, index) => {
      Promise.resolve(p).then(resolve).catch(err => {
        errors[index] = err
        count++
        if (count === promises.length) {
          reject(new AggregateError(errors, 'All promises were rejected'))
        }
      })
    })
  })
}

5. 手撕 Promise 并发限制(加分题)

场景: 有 100 个请求,最多同时发起 3 个

js 复制代码
async function limitConcurrency(tasks, limit) {
  const result = []
  const executing = new Set()

  for (const [index, task] of tasks.entries()) {
    const p = Promise.resolve().then(() => task()).then(res => {
      result[index] = res
      executing.delete(p)
    })
    executing.add(p)

    if (executing.size >= limit) {
      // 等待其中最快完成的那个
      await Promise.race(executing)
    }
  }

  await Promise.all(executing)
  return result
}

// 用法
const tasks = Array.from({ length: 10 }, (_, i) => 
  () => new Promise(resolve => setTimeout(() => resolve(i), Math.random() * 1000))
)
limitConcurrency(tasks, 3).then(console.log)

6. 手撕 Promise 重试机制(加分题)

js 复制代码
function retry(fn, times, delay = 0) {
  return new Promise((resolve, reject) => {
    function attempt(remaining) {
      fn()
        .then(resolve)
        .catch(err => {
          if (remaining <= 0) return reject(err)
          setTimeout(() => attempt(remaining - 1), delay)
        })
    }
    attempt(times)
  })
}

// 用法:最多重试3次,每次间隔1秒
retry(() => fetch('/api'), 3, 1000)
  .then(res => console.log('success'))
  .catch(err => console.log('failed after 3 retries'))

七、Promise 高频面试问答(背会直接用)

Q1:Promise 和 async/await 的关系?

async/await 是 Promise 的语法糖 ,底层还是基于 Promise 实现,让异步代码写法更像同步代码,同时使 try/catch 可以正常捕获异步错误。

js 复制代码
// 等价关系
async function fn() {
  const res = await somePromise
  return res
}

// 等价于
function fn() {
  return somePromise.then(res => res)
}

Q2:then 为什么可以链式调用?

因为 .then() / .catch() 每次都会返回一个新的 Promise,所以可以链式调用。这个新 Promise 的状态由回调函数的返回值决定:

  • 返回普通值 → 新 Promise fulfilled
  • 返回 Promise → 新 Promise 等待该 Promise
  • 抛出错误 → 新 Promise rejected

Q3:Promise 错误捕获方式有哪些?

  1. .catch() 捕获所有前面链上的错误(穿透特性)
  2. .then(null, onRejected) 只捕获当前链节点的错误
  3. try/catch 配合 async/await 使用
  4. 全局监听:window.addEventListener('unhandledrejection', handler)

Q4:all 和 allSettled 的区别?

特性 Promise.all Promise.allSettled
失败处理 一个失败,立即 reject(短路) 等全部完成,收集所有结果
适用场景 所有任务必须成功 需要知道每个任务的结果
返回值 成功值数组 {status, value/reason} 数组

Q5:Promise 的错误为什么不能被 try/catch 捕获?

js 复制代码
try {
  new Promise((_, reject) => {
    reject('err') // 这是异步的微任务,脱离了 try/catch 的同步执行上下文
  })
} catch (e) {
  console.log(e) // ❌ 不会执行
}

reject() 触发的是微任务 ,执行时 try/catch 的同步代码已经执行完毕,所以无法捕获。只有同步抛出的错误才能被 try/catch 捕获。

Q6:如何取消一个 Promise?

Promise 本身不支持取消,但有两种模拟方式:

js 复制代码
// 方式1:使用 AbortController(推荐,用于 fetch)
const controller = new AbortController()
fetch('/api', { signal: controller.signal })
controller.abort() // 取消请求

// 方式2:竞态 + 标志位
function cancellable(promise) {
  let isCancelled = false
  const wrapped = new Promise((resolve, reject) => {
    promise.then(
      val => !isCancelled && resolve(val),
      err => !isCancelled && reject(err)
    )
  })
  return {
    promise: wrapped,
    cancel() { isCancelled = true }
  }
}

Q7:Promise.resolve() 和 new Promise(resolve => resolve()) 有什么区别?

js 复制代码
// 如果传入的是普通值,完全等价
Promise.resolve(1)
// 等价于
new Promise(resolve => resolve(1))

// 如果传入的是 Promise,Promise.resolve 会直接返回该 Promise(同一个引用)
const p = Promise.resolve(new Promise(r => r(1)))
// p 就是传入的那个 Promise,而不是新创建的

Q8:微任务和宏任务的完整执行模型?

复制代码
事件循环(Event Loop)执行顺序:
1. 执行当前宏任务(包括同步代码)
2. 清空微任务队列(所有微任务,包括微任务执行过程中新增的微任务)
3. 执行下一个宏任务
4. 重复 2-3

微任务:Promise.then/catch/finally、queueMicrotask、MutationObserver
宏任务:setTimeout、setInterval、setImmediate、I/O、UI rendering

八、总结速查表

考点 关键结论
执行顺序 构造函数同步 → then/catch 微任务 → 宏任务
状态不可逆 pending → fulfilled/rejected,一旦修改无法回头
值穿透 .then() 参数非函数时,透传上一个 Promise 的值
catch 穿透 rejected 状态跳过所有 .then(),直到第一个 .catch()
finally 特性 不接收参数、不改变状态(除非内部 reject/throw)
return Promise then 中 return 一个 Promise,会额外消耗 2 个微任务 tick
all vs race all 全部成功,race 第一个完成
all vs allSettled all 短路,allSettled 等全部完成
race vs any race 第一个(含失败),any 第一个成功
错误捕获 .catch() 捕获链上错误,try/catch 只能配合 async/await

面试黄金法则: 看到输出题,先画微任务队列草图,标清每轮哪些微任务进队、哪些出队,一步步推导。

相关推荐
Mr.Rice.Fool1 小时前
rust面试经验1
后端·面试·职场和发展·rust
禧西3 小时前
面试准备——agent和大模型
面试·职场和发展
何陋轩3 小时前
Claude 3.5 vs GPT-4o vs Gemini:程序员应该选哪个?代码能力全面测评
人工智能·面试·架构
leoufung4 小时前
LeetCode 135. Candy:从直觉到最优解的完整推导
算法·leetcode·职场和发展
IT 青年5 小时前
网安面试经(13)
面试·网安
项管芝士5 小时前
PMP备考时间全攻略:如何把握关键节点,从容应对考试
职场和发展·职场发展
明天有专业课5 小时前
RAG-不写SQL也能查询MySQL数据
面试·aigc
leoufung6 小时前
LeetCode 50. Pow(x, n):从 O(n) 到 O(log n) 的快速幂彻底搞懂
算法·leetcode·职场和发展