Promise 从入门到精通
需求
作为前端开发,异步编程是我们每天都要面对的。而 Promise 就是解决异步问题的“终极武器”。无论你是刚入门,还是已经用过 async/await,搞懂 Promise 的原理和用法,都能让你写代码更自信、更优雅。
今天我们就用最直白的语言 + 最实用的代码,把 Promise 彻底讲透!
1. 为什么需要 Promise
先看一段“回调地狱”经典代码:
getUser((user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (detail) => {
console.log(detail);
});
});
});
层层嵌套,可读性差,难以维护,更别说错误处理了。Promise 就是为了解决这个问题而生的——它让异步操作变得像同步一样线性、可组合。
2. Promise 是什么
一个 Promise 对象代表一个异步操作的最终完成(或失败)及其结果值。
你可以把它想象成一个“承诺”:现在我还不能给你结果,但将来一定会告诉你(成功或失败)。
三个状态(生命周期)
- pending(进行中)——初始状态
- fulfilled(已成功)——操作成功,
resolve()被调用 - rejected(已失败)——操作失败,
reject()被调用
一旦状态从 pending 变为 fulfilled 或 rejected,就不可再变。
3. 基本用法:创建 Promise 并消费
创建一个 Promise
const myPromise = new Promise((resolve, reject) => {
// 执行异步任务(如 ajax、setTimeout、读取文件等)
setTimeout(() => {
const success = true;
if (success) {
resolve('数据加载成功 🎉');
} else {
reject('加载失败 😢');
}
}, 1000);
});
消费 Promise:.then() 和 .catch()
myPromise
.then((result) => {
console.log(result); // 1秒后输出:数据加载成功 🎉
})
.catch((error) => {
console.error(error);
});
4. 链式调用 —— 串行异步任务
这是 Promise 最强大的地方:
function getUser() {
return new Promise((resolve) => {
setTimeout(() => resolve({ id: 1, name: '张三' }), 500);
});
}
function getOrders(userId) {
return new Promise((resolve) => {
setTimeout(() => resolve(['订单A', '订单B']), 500);
});
}
getUser()
.then((user) => {
console.log(user); // { id: 1, name: '张三' }
return getOrders(user.id); // 返回新的 Promise
})
.then((orders) => {
console.log(orders); // ['订单A', '订单B']
})
.catch((err) => {
console.error('出错了:', err);
});
注意:.then() 中返回一个值,会被包装成 resolved Promise;返回另一个 Promise,会等待其完成。
5. 错误处理 —— 捕获所有异常
链式中的 .catch() 可以捕获前面任何一个环节抛出的错误,非常方便。
getUser()
.then((user) => {
if (!user) throw new Error('用户不存在');
return getOrders(user.id);
})
.then((orders) => {
// 如果订单为空,主动抛错
if (orders.length === 0) throw new Error('没有订单');
console.log(orders);
})
.catch((err) => {
console.error('捕获到错误:', err.message);
});
如果不用 .catch(),错误会被“吞掉”,所以永远别忘了加 catch!
6. 常用静态方法
清单
| 方法 | 作用 | 是否等待所有? |
|---|---|---|
Promise.resolve() |
立即返回成功状态的 Promise | ❌ |
Promise.reject() |
立即返回失败状态的 Promise | ❌ |
Promise.all() |
全部成功才成功,一个失败就失败 | ✅(并发) |
Promise.allSettled() |
等待全部完成(无论成败) | ✅(并发) |
Promise.race() |
谁先完成就返回谁 | ✅(并发) |
Promise.any() |
一个成功就返回,全部失败才失败 | ✅(并发) |
Promise.resolve(value) 快速创建一个状态已定的 Promise 对象
作用:将给定的值包装成一个 成功(fulfilled) 状态的 Promise。
Promise.resolve(value);
等价于:
new Promise((resolve) => resolve(value));
什么时候用?
- 当你有一个值,但后面的代码期望得到一个 Promise 时(统一接口)。
- 在拦截器或 .then 回调中,如果你想确保返回的一定是一个 Promise。
示例
// 场景:缓存里取数据
function getUser() {
const cache = { name: '张三' };
if (cache) {
// 直接返回缓存数据,但为了统一,包装成 Promise
return Promise.resolve(cache);
} else {
return fetch('/api/user');
}
}
getUser().then(user => console.log(user)); // 无论哪种情况,都能用 .then
Promise.reject(reason)
作用:返回一个 失败(rejected) 状态的 Promise,并携带错误原因 reason。
Promise.reject(reason);
等价于:
new Promise((resolve, reject) => reject(reason));
什么时候用?
- 在拦截器或函数中,检测到错误条件,希望提前终止后续的 Promise 链,并进入 .catch。
- 你代码中的 return Promise.reject(error) 就是最标准的用法。
例子:
function validateUser(user) {
if (!user || !user.id) {
// 参数校验失败,直接返回失败状态的 Promise,跳到 catch
return Promise.reject(new Error('用户信息无效'));
}
return fetch('/api/user/' + user.id);
}
validateUser(null)
.then(data => console.log(data))
.catch(err => console.error(err)); // 输出:用户信息无效
throw vs Promise.reject.
在 async 函数中,throw error 等价于 return Promise.reject(error)。
但在普通函数(非 async)的 .then 回调中:
throw error→ 会进入.catch(效果相同)。return Promise.reject(error)→ 也会进入.catch。
两者在 .then 回调中效果几乎一样,但 Promise.reject 更明确地表示“我要返回一个失败的 Promise”,而 throw 是抛出异常。
在你封装的拦截器中,因为回调函数可能是 async 也可能不是,Promise.reject 是一种更安全、更显式的写法。
Promise.all() —— 并发执行,全部成功才成功
const p1 = fetch('/api/user');
const p2 = fetch('/api/posts');
Promise.all([p1, p2])
.then(([userRes, postsRes]) => {
// 两个请求都成功后才执行
return Promise.all([userRes.json(), postsRes.json()]);
})
.then(([user, posts]) => {
console.log('用户', user);
console.log('文章', posts);
})
.catch((err) => {
// 只要有一个失败,就会进入这里
console.error('某个请求失败了', err);
});
Promise.race() —— 谁先完成就返回谁
const p1 = new Promise(resolve => setTimeout(resolve, 1000, '慢'));
const p2 = new Promise(resolve => setTimeout(resolve, 500, '快'));
Promise.race([p1, p2]).then(value => {
console.log(value); // 输出 '快'(因为 p2 先完成)
});
Promise.allSettled() —— 等待所有都完成(无论成功失败)
const p1 = Promise.resolve('成功');
const p2 = Promise.reject('失败');
Promise.allSettled([p1, p2]).then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: '成功' },
// { status: 'rejected', reason: '失败' }
// ]
});
Promise.any() —— 只要有一个成功就返回,全部失败才 reject
const p1 = Promise.reject('错误1');
const p2 = Promise.resolve('成功');
const p3 = Promise.reject('错误2');
Promise.any([p1, p2, p3]).then(value => {
console.log(value); // '成功'
});
7. async/await —— 更甜美的语法糖
虽然本博客是讲 Promise,但实际开发中我们几乎都使用 async/await,它让异步代码看起来更像同步。
async function fetchData() {
try {
const user = await getUser();
const orders = await getOrders(user.id);
console.log(orders);
} catch (error) {
console.error('捕获异常:', error);
}
}
await 后面跟一个 Promise,会阻塞后面的代码,直到 Promise 完成。async 函数默认返回一个 Promise。
注意:await 只能在 async 函数中使用,而且必须配合 try/catch 来处理错误,否则错误会被“静默”忽略。
8. 实际应用小案例
场景:并发请求数据,并在所有数据返回后统一渲染
async function initPage() {
const [user, posts, comments] = await Promise.all([
fetch('/user').then(res => res.json()),
fetch('/posts').then(res => res.json()),
fetch('/comments').then(res => res.json())
]);
renderUser(user);
renderPosts(posts);
renderComments(comments);
}
initPage().catch(err => console.error('页面加载失败', err));
场景:超时控制(例如请求超过3秒就中断)
function fetchWithTimeout(url, timeout = 3000) {
const controller = new AbortController(); // 可选,结合 fetch
const timer = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
);
const request = fetch(url, { signal: controller.signal });
return Promise.race([request, timer]);
}
fetchWithTimeout('/api/slow')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err.message));
9. 核心要点 & 最佳实践
| 要点 | 说明 |
|---|---|
| 始终返回 Promise | 在 .then() 中要么返回普通值(会自动包装),要么返回 Promise,以保持链式 |
| 永远不要忘记 .catch() | 或者用 try/catch 包裹 await,否则错误会被吞没 |
| Promise 创建后立即执行 | new Promise 中的代码是同步执行的,只有 .then 是异步回调 |
| 状态不可逆 | 一旦 resolve/reject,后续再调用无效 |
| 优先使用 async/await | 可读性更高,错误处理更自然,但底层依然是 Promise |
10. 总结
Promise 让异步编程变得可控、可读、可组合。无论你用 .then().catch() 还是 async/await,理解它的核心思想(状态、链式、错误传递)都能让你写出更健壮的代码。
如果你刚接触,不妨自己动手写几个例子,跑跑看,感觉一下“链式”和“并发”的魅力。如果已经熟悉,希望这篇也能帮你查漏补缺。
版权所有 © 【代码谷 - DMG】 欢迎非商用转载,转载请按下面格式注明出处,商业转载请联系授权,违者必究。(提示:点击下方内容复制出处)
源文: Promise 从入门到精通 ,链接:https://www.daimagu.com/article/promise.html,来源:代码谷 - DMG
评论