前端异步核心:Promise 从入门到吃透

前端异步核心:Promise 从入门到吃透

前言

在前端开发中,异步操作无处不在:定时器、接口请求、文件读取等。在 Promise 出现之前,我们只能依靠 ** 回调函数(callback)** 处理异步逻辑,但多个异步依次执行时就会陷入 "回调地狱",代码可读性、可维护性极差。

本文将从零开始,带你彻底理解 Promise:从诞生背景、核心状态、基础用法、链式调用、错误捕获,到静态方法、事件循环、async/await 语法糖,覆盖日常开发与面试所有高频考点,一篇吃透 Promise。


一、为什么需要 Promise?

1. 回调函数与回调地狱

没有 Promise 时,异步操作依赖回调函数实现:

js

javascript 复制代码
setTimeout(() => {
  console.log('1秒后执行')
}, 1000)

当需要依次执行多个异步操作 时,代码会层层嵌套,形成回调地狱(Callback Hell)

js

javascript 复制代码
setTimeout(() => {
  console.log(1)
  setTimeout(() => {
    console.log(2)
    setTimeout(() => {
      console.log(3)
    }, 1000)
  }, 1000)
}, 1000)

2. 回调地狱的缺点

  • 代码嵌套极深,可读性极差
  • 错误处理混乱,难以统一捕获
  • 逻辑难以复用、组合
  • 业务复杂后完全难以维护

Promise 正是为解决这一问题诞生,它是专门管理异步操作的对象,能让异步代码写得像同步一样扁平清晰。


二、Promise 核心:三种状态

Promise 实例一生只有三种状态,且状态一旦改变就不可逆

三种状态详解

  1. **pending(等待中)**刚创建时的默认状态,异步操作正在执行,既未成功也未失败。
  2. **fulfilled(已成功 / 已兑现)**异步操作执行成功,状态从 pending → fulfilled
  3. **rejected(已失败 / 已拒绝)**异步操作执行失败,状态从 pending → rejected

状态变化规则

  • 仅两种合法变化:pending → fulfilledpending → rejected
  • 状态变更后固定不变,成功后不能再失败,失败后也不能再成功。

三、Promise 基础用法

1. 创建 Promise

Promise 是构造函数,需通过 new Promise() 创建,接收一个执行器函数(executor)

js

javascript 复制代码
const p = new Promise((resolve, reject) => {
  // 异步逻辑写在这里
})

执行器函数包含两个核心参数:

  • resolve(value):调用后状态变为 fulfilled,并向外传递成功结果
  • reject(reason):调用后状态变为 rejected,并向外传递失败原因

简单示例:

js

javascript 复制代码
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    // 异步成功
    resolve('请求成功')
    
    // 异步失败
    // reject('请求失败')
  }, 1000)
})

这一步仅创建并启动异步,无法直接获取结果

2. 获取结果:.then () 和 .catch ()

通过 .then() 接收成功结果,.catch() 接收失败原因:

js

javascript 复制代码
p
  .then(res => {
    console.log('成功:', res)
  })
  .catch(err => {
    console.log('失败:', err)
  })
  • .then() 对应 resolve()
  • .catch() 对应 reject()

四、Promise 核心能力:链式调用

链式调用是 Promise 解决回调地狱的关键,让异步代码保持扁平结构。

1. 链式调用原理

  • 每个 .then() 都会返回一个新的 Promise
  • 下一个 .then() 接收上一个 .then() 的返回值
  • 可无限链式书写,逻辑清晰

2. 链式调用示例

js

javascript 复制代码
new Promise((resolve) => {
  setTimeout(() => {
    console.log('第一步')
    resolve(1)
  }, 1000)
})
.then(res => {
  console.log('接收:', res)
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('第二步')
      resolve(2)
    }, 1000)
  })
})
.then(res => {
  console.log('接收:', res)
  console.log('第三步')
})

3. 简写:返回普通值

.then() 中直接 return 普通值,会自动包装为 Promise.resolve(值)

js

javascript 复制代码
Promise.resolve()
.then(() => {
  return 123
})
.then(res => {
  console.log(res) // 123
})

五、Promise 错误捕获机制

Promise 支持两种错误捕获方式,推荐统一使用 .catch()

1. 方式一:then 第二个参数

仅能捕获当前 Promise 的错误,无法捕获整条链:

js

javascript 复制代码
new Promise((resolve, reject) => {
  reject('出错了')
})
.then(
  res => {}, 
  err => { console.log('失败:', err) } 
)

2. 方式二:统一 catch(推荐)

可捕获整条链式调用中任意位置的错误:

js

javascript 复制代码
new Promise((resolve, reject) => {
  reject('出错了')
})
.then(res => {})
.catch(err => {
  console.log('捕获错误:', err)
})

3. 捕获特点

  • 任意环节报错,会跳过后续所有 then,直接进入 catch
  • catch 执行后,仍可继续链式调用 then
  • 可捕获:reject、throw new Error、then 内代码异常

六、Promise 常用静态方法

Promise 提供多个直接调用的静态方法,无需 new,是面试与开发高频考点。

1. Promise.resolve()

快速创建一个直接成功的 Promise:

js

javascript 复制代码
Promise.resolve(100).then(res => console.log(res)) // 100

2. Promise.reject()

快速创建一个直接失败的 Promise:

js

javascript 复制代码
Promise.reject('错误').catch(err => console.log(err)) // 错误

3. Promise.all()

接收 Promise 数组,全部成功才成功,任一失败则整体失败

  • 成功:返回与传入顺序一致的结果数组
  • 失败:返回第一个失败的原因

js

javascript 复制代码
const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
Promise.all([p1, p2]).then(res => console.log(res)) // [1,2]

4. Promise.race()

"赛跑机制":谁先完成(成功 / 失败)就返回谁,常用于接口超时控制。

5. Promise.allSettled()

等待所有 Promise 执行结束,无论成功失败,均返回包含状态与结果的数组,适合批量请求场景。

6. Promise.any()

all 相反:任一成功则整体成功,全部失败才最终失败 ,失败时抛出 AggregateError


七、finally 与 微任务机制

1. finally 用法

无论 Promise 成功或失败,.finally() 内逻辑最终一定执行,常用于关闭加载状态、清理资源:

js

javascript 复制代码
new Promise((resolve) => resolve('ok'))
.then(res => {})
.catch(err => {})
.finally(() => {
  console.log('无论如何都执行')
})

2. Promise 与微任务(Event Loop 必考)

JavaScript 是单线程语言,依靠事件循环(Event Loop)执行异步任务,执行优先级:同步任务 → 微任务 → 宏任务

  • 微任务:Promise、queueMicrotask、async/await
  • 宏任务:setTimeout、setInterval、ajax 等

经典面试题:

js

javascript 复制代码
console.log(1)
setTimeout(() => console.log(2), 0)
Promise.resolve().then(() => console.log(3))
console.log(4)

执行顺序:1 → 4 → 3 → 2

3. 中断 Promise 链

.then() 中返回一个永久 pending 的 Promise,即可中断后续链式调用:

js

javascript 复制代码
.then(() => {
  return new Promise(() => {})
})
.then(() => {
  // 永远不会执行
})

八、async/await:Promise 终极语法糖

async/await 是 Promise 的语法糖,让异步代码写法完全趋近于同步,是目前前端开发主流写法。

1. 基础规则

  • async 写在函数前,函数内可使用 await
  • await 后只能跟 Promise 或 thenable 对象
  • await 会暂停执行,直到 Promise 返回结果

2. 基础示例

js

javascript 复制代码
// 封装返回 Promise 的异步函数
function delay(time) {
  return new Promise(resolve => {
    setTimeout(() => resolve('完成'), time)
  })
}

// async/await 调用
async function test() {
  console.log('开始')
  const res = await delay(1000)
  console.log(res)
  console.log('结束')
}
test()

3. async 函数返回值

  • return 普通值 → 自动包装为 Promise.resolve(值)
  • 抛出错误 → 自动包装为 Promise.reject(错误)

4. 错误处理

推荐使用 try/catch 统一捕获:

js

javascript 复制代码
async function test() {
  try {
    const res = await Promise.reject('出错')
  } catch (err) {
    console.log(err)
  }
}

5. 并行执行优化

多个异步串行 会浪费性能,使用 Promise.all 实现并行:

js

scss 复制代码
const [res1, res2, res3] = await Promise.all([p1(), p2(), p3()])

结语

Promise 是前端异步编程的基石,从解决回调地狱,到链式调用、统一错误处理,再到 async/await 语法糖,贯穿日常开发与面试全过程。

本文覆盖 Promise 所有核心知识点,吃透后可轻松应对接口请求、并发控制、面试手撕源码等场景,是前端进阶必备技能。

相关推荐
开源盛世!!2 小时前
3.19-3.21
linux·服务器·前端
必胜刻2 小时前
AJAX 请求理解
前端·ajax·okhttp·前后端交互
小民AI实战笔记2 小时前
NVM实战指南:高效管理你的Node.js环境
前端·node.js
朱建伟2 小时前
大神尤雨溪再次出手,前端工具链整合--该文章是对vite plus官方README文档进行了翻译
前端·vite
vball2 小时前
宏观数据从哪里来?——主流宏观经济数据库与API全景
前端
吠品2 小时前
Vue项目Moment.js引入优化:全局挂载与按需引入的深度解析与最佳实践
前端·javascript·vue.js
不甜情歌2 小时前
JS 类型判断不用愁:4 种方法,覆盖所有场景
前端·javascript
ETA82 小时前
状态管理没那么复杂:手写实现 Zustand 核心逻辑
前端·react.js
用户255778850812 小时前
axios请求缓存
前端