Skip to content

Promise 异步编程面试题全解析

一、核心要点速览

💡 核心考点

  • 三种状态: Pending、Fulfilled、Rejected(状态不可逆)
  • 链式调用: then/catch/finally 返回新 Promise
  • 常用方法: all、race、allSettled、any
  • 错误处理: 捕获异常、错误传播

二、Promise 基础

1. 三种状态

javascript
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = true
    if (success) {
      resolve('成功结果')
    } else {
      reject('失败原因')
    }
  }, 1000)
})

// 状态转换:
// Pending → Fulfilled (resolve)
// Pending → Rejected (reject)
// ⚠️ 状态只能改变一次,不可逆

2. 状态机图示

┌──────────────────────────────────────────────────────────┐
│                    Promise 状态机                         │
└──────────────────────────────────────────────────────────┘

状态转换图:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
          ┌─────────────┐
          │ PENDING     │
          │ (等待中)     │
          └──────┬──────┘

        ┌────────┴────────┐
        │                 │
        ▼                 ▼
┌───────────────┐ ┌───────────────┐
│ FULFILLED     │ │ REJECTED      │
│ (已成功)      │ │ (已失败)      │
│ ✓             │ │ ✗             │
└───────────────┘ └───────────────┘

特点:
✓ 状态只能改变一次 (不可逆)
✓ Pending → Fulfilled (resolve)
✓ Pending → Rejected (reject)
✗ Fulfilled/Rejected 不能互相转换
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

3. 基本使用

javascript
promise
  .then(result => {
    console.log('成功:', result)
    return result
  })
  .catch(error => {
    console.error('失败:', error)
    throw error // 可以继续传播
  })
  .finally(() => {
    console.log('总是执行')
  })

三、链式调用

1. then 的返回值

javascript
// then 总是返回新 Promise
Promise.resolve(1)
  .then(x => x + 1)        // 返回普通值 → 自动包装
  .then(x => x * 2)        // 2 * 2 = 4
  .then(x => Promise.resolve(x + 1)) // 返回 Promise
  .then(x => console.log(x)) // 5

// 链式调用本质
const p1 = Promise.resolve(1)
const p2 = p1.then(x => x + 1) // p2 是新 Promise
const p3 = p2.then(x => x * 2) // p3 是另一个新 Promise

// 每个 then 都创建新 Promise
// 实现同步调用异步结果

2. 链式调用时序图

时间 →  ─────────────────────────────────────────────────►

Promise 链执行流程:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
创建 Promise


┌─────────────────┐
│ new Promise     │
└────────┬────────┘


    ┌─────────┐
    │ pending │
    └─────────┘

         │ 1 秒后 resolve(10)

    ┌─────────────┐
    │ fulfilled   │
    │ value: 10   │
    └─────────────┘


    .then(x => x * 2)


    ┌─────────────┐
    │ pending     │
    └─────────────┘

         │ resolve(20)

    ┌─────────────┐
    │ fulfilled   │
    │ value: 20   │
    └─────────────┘


    .then(x => x + 5)


    ┌─────────────┐
    │ fulfilled   │
    │ value: 25   │
    └─────────────┘

链式调用优势:
  ✓ 避免回调地狱
  ✓ 错误统一处理
  ✓ 代码可读性强
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

3. 错误传播

javascript
// 错误会沿着链向下传播
Promise.resolve(1)
  .then(x => {
    throw new Error('错误 1')
  })
  .then(x => {
    // 不会执行,跳过
  })
  .catch(err => {
    console.error(err.message) // '错误 1'
    return 100 // 返回新值,继续链
  })
  .then(x => {
    console.log(x) // 100
    throw new Error('错误 2')
  })
  .catch(err => {
    console.error(err.message) // '错误 2'
  })

// 最佳实践:链末尾加 catch
fetchData()
  .then(process)
  .then(save)
  .catch(error => {
    // 捕获整个链的错误
    console.error('操作失败:', error)
  })

四、Promise 常用方法

1. Promise.all(全部成功)

javascript
// 所有 Promise 都成功才成功,一个失败就失败
Promise.all([p1, p2, p3])
  .then(results => {
    // results = [result1, result2, result3]
    console.log('全部成功:', results)
  })
  .catch(error => {
    // 任意一个失败就到这里
    console.error('有一个失败:', error)
  })

// 实际应用:并发请求
const [user, posts, comments] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),
  fetch('/api/comments').then(r => r.json())
])

// 性能优化:并行执行
// 总耗时 = max(p1, p2, p3),而非 sum

2. Promise.all 执行流程图

┌──────────────────────────────────────────────────────────┐
│                   Promise.all 执行流程                    │
└──────────────────────────────────────────────────────────┘

成功场景:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
并发发起请求

    ├────► p1 请求 ────┐
    │                 │
    ├────► p2 请求 ────┼─► 陆续完成
    │                 │
    └────► p3 请求 ────┘


                  ┌───────────┐
                  │ 全部完成  │
                  │ ✓ ✓ ✓    │
                  └───────────┘


                  .then([
                    userRes,
                    postsRes,
                    commentsRes
                  ])
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

失败场景 (假设 p2 失败):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
时间 →  ─────────────────────────────────────────►

    ├────► p1 请求 ────► ✓

    ├────► p2 请求 ────► ✗ Error!
    │                      │
    │                      ▼
    │                立即触发 catch

    └────► p3 请求 ────► ? (仍会完成但被忽略)

特点:
✓ 短路机制:一个失败,整体失败
✓ 结果顺序:与 Promise 顺序一致(非完成顺序)
✓ 适用场景:依赖多个结果的场景
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

3. Promise.race(竞赛)

javascript
// 第一个完成就完成(无论成功失败)
Promise.race([p1, p2, p3])
  .then(firstResult => {
    console.log('第一个完成:', firstResult)
  })
  .catch(firstError => {
    console.error('第一个失败:', firstError)
  })

// 应用:超时控制
function fetchWithTimeout(url, timeout = 5000) {
  const fetchPromise = fetch(url)
  
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error(`请求超时 (${timeout}ms)`))
    }, timeout)
  })
  
  return Promise.race([fetchPromise, timeoutPromise])
}

// 使用
fetchWithTimeout('/api/data', 3000)
  .then(res => res.json())
  .catch(err => console.error(err))

4. Promise.allSettled(等待所有完成)

javascript
// 等待所有 Promise 完成,不关心成败
Promise.allSettled([p1, p2, p3])
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log('成功:', result.value)
      } else {
        console.error('失败:', result.reason)
      }
    })
  })

// 应用:需要知道每个请求的结果
const results = await Promise.allSettled([
  fetch('/api/user'),
  fetch('/api/posts'),
  fetch('/api/comments')
])

results.forEach((result, index) => {
  if (result.status === 'fulfilled') {
    console.log(`请求${index}成功:`, result.value)
  } else {
    console.error(`请求${index}失败:`, result.reason)
  }
})

5. Promise.any(任意成功)

javascript
// 第一个成功就成功,都失败才失败
Promise.any([p1, p2, p3])
  .then(firstSuccess => {
    console.log('第一个成功的:', firstSuccess)
  })
  .catch(errors => {
    // AggregateError: 所有错误
    console.error('都失败了:', errors.errors)
  })

// 应用:多 CDN 备份
const urls = [
  'https://cdn1.example.com/file.js',
  'https://cdn2.example.com/file.js',
  'https://cdn3.example.com/file.js'
]

const scripts = urls.map(url => 
  new Promise((resolve, reject) => {
    const script = document.createElement('script')
    script.src = url
    script.onload = () => resolve(script)
    script.onerror = () => reject(new Error(`${url} 加载失败`))
    document.head.appendChild(script)
  })
)

try {
  const firstLoaded = await Promise.any(scripts)
  console.log('加载成功:', firstLoaded)
} catch (error) {
  console.error('所有 CDN 都失败了')
}

五、手写 Promise

1. 简化版实现

javascript
class MyPromise {
  constructor(executor) {
    this.state = 'pending'
    this.value = undefined
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []
    
    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        this.onFulfilledCallbacks.forEach(fn => fn())
      }
    }
    
    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
  
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' 
      ? onFulfilled 
      : value => value
    
    onRejected = typeof onRejected === 'function' 
      ? onRejected 
      : reason => { throw reason }
    
    return new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        try {
          const x = onFulfilled(this.value)
          resolve(x)
        } catch (error) {
          reject(error)
        }
      } else if (this.state === 'rejected') {
        try {
          const x = onRejected(this.reason)
          resolve(x)
        } catch (error) {
          reject(error)
        }
      } else {
        // pending
        this.onFulfilledCallbacks.push(() => {
          try {
            const x = onFulfilled(this.value)
            resolve(x)
          } catch (error) {
            reject(error)
          }
        })
        
        this.onRejectedCallbacks.push(() => {
          try {
            const x = onRejected(this.reason)
            resolve(x)
          } catch (error) {
            reject(error)
          }
        })
      }
    })
  }
  
  catch(onRejected) {
    return this.then(null, onRejected)
  }
  
  finally(callback) {
    return this.then(
      value => Promise.resolve(callback()).then(() => value),
      reason => Promise.resolve(callback()).then(() => { throw reason })
    )
  }
}

// 静态方法
MyPromise.resolve = function(value) {
  return new MyPromise(resolve => resolve(value))
}

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

MyPromise.all = function(promises) {
  return new MyPromise((resolve, reject) => {
    const results = []
    let completed = 0
    
    promises.forEach((p, i) => {
      p.then(value => {
        results[i] = value
        completed++
        if (completed === promises.length) {
          resolve(results)
        }
      }, reject)
    })
  })
}

六、面试标准回答

Promise 是 ES6 引入的异步编程解决方案,有三种状态:Pending(等待中)、Fulfilled(已成功)、Rejected(已失败)。

核心特点

  1. 状态只能改变一次,不可逆
  2. then/catch/always 返回新 Promise,支持链式调用
  3. 可以聚合多个 Promise(all、race 等)

解决了回调地狱问题

javascript
// 回调地狱
getData(a => {
  processData(a, b => {
    saveData(b, c => {
      console.log('完成')
    })
  })
})

// Promise 链式
getData()
  .then(processData)
  .then(saveData)
  .then(() => console.log('完成'))

常用方法

  • all: 所有都成功才成功,一个失败就失败(短路)
  • race: 第一个完成就完成(竞赛)
  • allSettled: 等待所有完成,返回各自结果
  • any: 第一个成功就成功,都失败才失败

错误处理通过 catch 实现,错误会沿着链向下传播,直到被捕获。最佳实践是在链末尾添加统一的错误处理。

实际项目中,我经常使用 Promise.all 来并发请求多个接口,使用 race 来实现超时控制,使用 allSettled 来处理需要知道每个请求结果的场景。


七、记忆口诀

Promise 歌诀:

Promise 有三种态,
pending fulfilled rejected。
状态只能变一次,
链式调用解千愁。

then 接成功 catch 错,
finally 总是被执行。
all 要全都成功,
race 比谁快第一!

allSettled 等全部,
any 要一个成功。
错误传播向下走,
链尾记得加 catch!

八、推荐资源


九、总结一句话

  • Promise: 状态机 + 链式调用 = 异步编程标准化
  • all/race: 聚合并发 + 灵活控制 = 并发请求利器 🚀
  • 错误处理: catch 捕获 + 错误传播 = 健壮的异步代码
最近更新