假设打开一个页面时,需要依次完成三件事:
- 获取用户信息
- 根据用户信息查询订单
- 根据订单加载商品详情
如果全部依赖回调函数,代码很快就会变成这样:
getUser(user => {
getOrders(user.id, orders => {
getProduct(orders[0].productId, product => {
console.log(product)
})
})
})
代码越写越深,错误处理也越来越麻烦。这就是常说的“回调地狱”。
Promise 的价值,就是把这种层层嵌套的异步代码,整理成更清晰的链式流程。
本文会带你掌握:
- Promise 的三种状态
then、catch和finally- Promise 链式调用的规则
all、allSettled、race、any的区别async/await与 Promise 的关系
一、Promise 到底是什么?
JavaScript 主线程一次只能执行一个任务,但网络请求、定时器和浏览器事件显然不能一直阻塞主线程。
这些耗时操作会交给浏览器或运行环境处理,完成后再通知 JavaScript。
Promise 本质上是一个用来描述异步操作最终结果的对象。
你可以把它理解成一张“取餐小票”:
- 刚下单:等待中
- 餐做好了:成功
- 制作失败:拒绝
对应到 Promise,就是三种状态:
| 状态 | 含义 |
|---|---|
pending | 等待中 |
fulfilled | 操作成功 |
rejected | 操作失败 |
二、创建一个 Promise
Promise 是构造函数,需要通过 new 创建实例:
const promise = new Promise((resolve, reject) => {
// 执行异步操作
})
构造函数接收一个执行器函数,执行器函数又接收两个参数:
resolve(value):将 Promise 变为成功状态reject(reason):将 Promise 变为失败状态
例如:
const promise = new Promise((resolve, reject) => {
const requestSuccess = true
if (requestSuccess) {
resolve('数据请求成功')
} else {
reject(new Error('数据请求失败'))
}
})
需要特别注意:Promise 执行器会立即、同步执行。
console.log('开始')
new Promise(resolve => {
console.log('执行 Promise')
resolve()
})
console.log('结束')
输出结果为:
开始
执行 Promise
结束
Promise 并不会自动让代码变成异步。真正异步的是网络请求、定时器等具体操作。
三、Promise 状态一旦改变,就不会再次变化
Promise 的状态只能发生下面两种变化:
pending → fulfilled
pending → rejected
状态一旦确定,就不能再次改变:
const promise = new Promise((resolve, reject) => {
resolve('成功')
reject(new Error('失败'))
resolve('再次成功')
})
promise.then(value => {
console.log(value)
})
最终只会输出:
成功
第一次调用 resolve 后,Promise 已经变为 fulfilled,后面的 reject 和 resolve 都不会再生效。
四、用 then 获取成功结果
then 用于注册 Promise 成功或失败后的处理函数:
promise.then(
value => {
console.log('成功:', value)
},
reason => {
console.log('失败:', reason)
}
)
第一个参数处理成功结果,第二个参数处理失败原因。
不过实际开发中,更推荐使用 then + catch:
promise
.then(value => {
console.log('成功:', value)
})
.catch(error => {
console.error('失败:', error)
})
这样不仅结构更清晰,catch 还能捕获前面 then 中抛出的错误。
五、真正理解 Promise 链式调用
假设有三个请求,后一个请求依赖前一个请求的结果。
回调写法可能是这样:
getData('/api/user?id=1', user => {
getData(`/api/orders?userId=${user.id}`, orders => {
getData(`/api/product?id=${orders[0].productId}`, product => {
console.log(product)
})
})
})
如果 getData 返回 Promise,就可以改写为:
getData('/api/user?id=1')
.then(user => {
return getData(`/api/orders?userId=${user.id}`)
})
.then(orders => {
return getData(`/api/product?id=${orders[0].productId}`)
})
.then(product => {
console.log(product)
})
.catch(error => {
console.error('请求失败:', error)
})
嵌套结构被展开成了一条清晰的执行链。
为什么可以一直调用 then?
因为每次调用 then,都会返回一个新的 Promise:
const nextPromise = promise.then(value => {
return value
})
console.log(nextPromise instanceof Promise) // true
新 Promise 的状态,取决于回调函数的执行结果。
情况一:返回普通值
Promise.resolve(1)
.then(value => {
return value + 1
})
.then(value => {
console.log(value) // 2
})
回调返回普通值时,下一个 Promise 会变为成功,成功值就是这个返回值。
情况二:返回另一个 Promise
Promise.resolve()
.then(() => {
return new Promise(resolve => {
setTimeout(() => resolve('请求完成'), 1000)
})
})
.then(value => {
console.log(value)
})
链条会等待返回的 Promise 得到结果,再继续向后执行。
情况三:抛出错误
Promise.resolve()
.then(() => {
throw new Error('处理数据时出错')
})
.catch(error => {
console.error(error.message)
})
只要回调中抛出错误,下一个 Promise 就会变为 rejected。
因此,写 Promise 链时最重要的一条规则是:
需要把结果交给下一步时,一定要
return。
六、catch:统一处理链条中的错误
下面这段代码中,then 内访问了一个不存在的变量:
Promise.resolve('ok')
.then(value => {
console.log(value)
console.log(notDefined)
})
.catch(error => {
console.error('捕获错误:', error.message)
})
catch 不仅能捕获原始 Promise 的失败,也能捕获前面任意一个 then 中抛出的错误。
它基本等价于:
promise.then(undefined, error => {
console.error(error)
})
但需要注意,catch 并不是让错误消失,而是让我们有机会处理失败。
如果 catch 正常返回,后面的 Promise 链还可以继续执行:
Promise.reject(new Error('请求失败'))
.catch(error => {
console.error(error.message)
return '使用默认数据'
})
.then(value => {
console.log(value) // 使用默认数据
})
如果希望错误继续向后传递,需要再次抛出:
.catch(error => {
console.error('记录错误:', error)
throw error
})
七、finally:无论成功失败都会执行
有些操作无论请求成功还是失败都必须执行,例如:
- 关闭加载动画
- 恢复按钮状态
- 释放资源
这时可以使用 finally:
showLoading()
getData('/api/user')
.then(data => {
console.log(data)
})
.catch(error => {
console.error(error)
})
.finally(() => {
hideLoading()
})
finally 不接收成功值或失败原因,它通常只负责执行清理工作。
八、Promise.all:全部成功才算成功
当多个异步任务互不依赖,可以同时发起:
const userPromise = getData('/api/user')
const menuPromise = getData('/api/menu')
const messagePromise = getData('/api/messages')
Promise.all([userPromise, menuPromise, messagePromise])
.then(([user, menu, messages]) => {
console.log(user, menu, messages)
})
.catch(error => {
console.error('至少有一个请求失败:', error)
})
Promise.all 的特点:
- 所有任务成功,整体才成功
- 返回值顺序与传入顺序一致
- 任意一个任务失败,整体立即失败
它适合“缺一不可”的场景,例如页面必须同时拿到用户、权限和配置数据后才能渲染。
九、Promise.allSettled:无论成功失败,都等到最后
如果我们希望知道每个任务的最终结果,可以使用 Promise.allSettled:
const tasks = [
Promise.resolve('用户信息加载成功'),
Promise.reject(new Error('广告加载失败')),
Promise.resolve('推荐内容加载成功')
]
Promise.allSettled(tasks).then(results => {
console.log(results)
})
结果结构类似:
[
{
status: 'fulfilled',
value: '用户信息加载成功'
},
{
status: 'rejected',
reason: Error('广告加载失败')
},
{
status: 'fulfilled',
value: '推荐内容加载成功'
}
]
它适合“部分失败也不影响整体”的场景,例如批量上传、多个模块独立加载。
十、Promise.race:谁先结束,就采用谁的结果
race 是“赛跑”的意思。
它会采用第一个落定的 Promise,无论这个 Promise 是成功还是失败:
const request = new Promise(resolve => {
setTimeout(() => resolve('请求成功'), 3000)
})
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('请求超时')), 2000)
})
Promise.race([request, timeout])
.then(value => {
console.log(value)
})
.catch(error => {
console.error(error.message) // 请求超时
})
这里决定结果的是完成速度,与 Promise 在数组中的位置无关。
Promise.race 经常被用来实现请求超时控制。
十一、Promise.any:只要一个成功即可
Promise.any 会返回第一个成功的结果:
const serverA = Promise.reject(new Error('服务器 A 失败'))
const serverB = Promise.resolve('服务器 B 返回数据')
const serverC = Promise.resolve('服务器 C 返回数据')
Promise.any([serverA, serverB, serverC])
.then(value => {
console.log(value) // 服务器 B 返回数据
})
.catch(error => {
console.error(error)
})
只有全部 Promise 都失败时,Promise.any 才会失败,并抛出 AggregateError。
它适合多个备用服务之间“任意一个成功即可”的场景。
十二、四种组合方法怎么选?
| 方法 | 成功条件 | 失败条件 | 典型场景 |
|---|---|---|---|
Promise.all | 全部成功 | 任意一个失败 | 多个数据缺一不可 |
Promise.allSettled | 永远成功返回结果列表 | 不会因单个任务失败而失败 | 批量任务、独立模块 |
Promise.race | 最先落定的是成功 | 最先落定的是失败 | 超时控制、竞速 |
Promise.any | 任意一个成功 | 全部失败 | 多个备用服务 |
一句话记忆:
all:一个都不能少allSettled:无论成败都要结果race:谁最快听谁的any:谁先成功用谁
十三、Promise.resolve 与 Promise.reject
Promise.resolve
创建一个成功状态的 Promise:
Promise.resolve('成功').then(value => {
console.log(value)
})
如果传入的本身就是 Promise,Promise.resolve 会采用它的最终状态:
const original = Promise.resolve('ok')
const result = Promise.resolve(original)
console.log(result === original) // true
Promise.reject
创建一个失败状态的 Promise:
Promise.reject(new Error('操作失败'))
.catch(error => {
console.error(error.message)
})
实际开发中,建议使用 Error 对象作为失败原因,这样可以保留错误信息和调用栈。
十四、async/await:Promise 的另一种写法
async/await 并没有取代 Promise,它只是让 Promise 代码看起来更像同步代码。
前面的链式调用:
getData('/api/user?id=1')
.then(user => {
return getData(`/api/orders?userId=${user.id}`)
})
.then(orders => {
return getData(`/api/product?id=${orders[0].productId}`)
})
.then(product => {
console.log(product)
})
.catch(error => {
console.error(error)
})
可以改写为:
async function loadProduct() {
try {
const user = await getData('/api/user?id=1')
const orders = await getData(`/api/orders?userId=${user.id}`)
const product = await getData(
`/api/product?id=${orders[0].productId}`
)
console.log(product)
} catch (error) {
console.error('请求失败:', error)
}
}
loadProduct()
需要记住三点:
async函数一定会返回 Promiseawait通常写在async函数中await遇到 rejected Promise 时会抛出异常,可以使用try...catch捕获
多个没有依赖关系的任务,不要逐个 await:
// 串行执行,速度较慢
const user = await getUser()
const menu = await getMenu()
可以让它们并行执行:
const [user, menu] = await Promise.all([
getUser(),
getMenu()
])
十五、最后总结
学完 Promise,至少要记住下面几件事:
- Promise 有
pending、fulfilled、rejected三种状态 - 状态一旦从
pending改变,就不能再次改变 - Promise 执行器会立即同步执行,Promise 本身不等于异步
- 每次调用
then都会返回一个新的 Promise - Promise 链中需要把结果交给下一步时,记得
return catch可以统一处理链条中的失败和异常finally适合执行清理操作- 多个独立任务可以使用
all、allSettled、race或any async/await本质上仍然建立在 Promise 之上
真正掌握 Promise,不是记住所有 API,而是理解一个核心:
每一步都返回一个结果,下一步根据这个结果继续执行。
当你理解了这条规则,Promise 链、错误传递和 async/await 就都会变得清晰。
我的个人博客:XieJinYang 的博客
如果这篇文章帮你理清了 Promise,欢迎点赞、收藏。下一次遇到异步代码时,希望你不再被层层回调绕晕。