首先,需要了解Promise英文意思 :**承诺,**那么核心就来了。
核心思想:一个“承诺”
下面我们来简单分析一下
一、为什么需要 Promise?—— 从“回调地狱”说起
在讲解 Promise 是什么之前,我们必须先知道它解决了什么问题。
想象一个场景:你要做一顿饭,步骤是:
-
烧水(需要5分钟)
-
水烧开后,煮面(需要3分钟)
-
面煮好后,吃面(需要10分钟)
在 JavaScript 的世界里,像烧水、煮面这类需要等待的操作,都是异步操作。如果我们用传统的回调函数(Callback) 来写,代码会变成这样:
烧水(function(水) {
煮面(水, function(面) {
吃面(面, function() {
console.log('终于吃完了!');
});
});
});
你看,为了确保顺序(水开才能煮面,面好才能吃),我们不得不把函数一层套一层。这仅仅三层嵌套,已经看起来有点乱了。
如果业务更复杂呢?比如吃完面还要洗碗、扔垃圾、看电视……代码就会变成:
烧水(() => {
煮面(() => {
吃面(() => {
洗碗(() => {
扔垃圾(() => {
看电视(() => {
// ... 无限嵌套,形成“金字塔”
});
});
});
});
});
});
这种代码被称为 “回调地狱(Callback Hell)” 或 “厄运金字塔” 。它有很多缺点:
-
难以阅读和维护:缩进越来越多,代码向右“飞”得越来越远。
-
错误处理困难:很难在每一层都统一处理错误。
哈哈,到这里就说完了 promise 出生原因了,为什么需要它了。时势造英雄
二、Promise 是什么?—— 一个“承诺”
Promise(承诺) 就是一个对象,它代表一个异步操作的最终完成(或失败)及其结果值。
把它想象成现实生活中的“承诺”或“订单”最好理解:
你去快餐店点了一个汉堡。
收银员不会直接把汉堡给你,而是会给你一张小票(Promise)。
这张小票就是一个承诺,它承诺你:
最终你会得到汉堡(成功状态,Fulfilled)。
或者因为某种原因(比如卖完了)你会得到退款和道歉(失败状态,Rejected)。
在等待的过程中,小票的状态是制作中( pending )。
拿到小票后,你不用傻傻地等在柜台前。你可以去玩手机(执行其他任务)。当汉堡做好了,小票会提醒你(通过
.then()方法)来取餐。
三、Promise 的三种状态
一个 Promise 对象必然处于以下三种状态之一:
-
pending(等待中):初始状态,既不是成功,也不是失败。
-
fulfilled(已成功):操作成功完成。
-
rejected(已失败):操作失败。
状态一旦改变,就凝固了,不会再变:
-
只能从
pending->fulfilled -
或者从
pending->rejected
具体详解可以从这里查看 :developer.mozilla.org/zh-CN/docs/…
既然知道Promise了,那么现在我们就可以简单使用哈...
四、如何创建一个 Promise?
使用 new Promise() 构造函数,它接受一个函数(执行器)作为参数。这个执行器函数自己有两个参数:resolve 和 reject(这两个也是函数)。
-
resolve(value):当异步操作成功时调用,将状态从pending改为fulfilled,并将结果value传递出去。 -
reject(error):当异步操作失败时调用,将状态从pending改为rejected,并将错误信息error传递出去。
示例:用 Promise 包装一个简单的异步操作(setTimeout)
const 做汉堡 = new Promise((resolve, reject) => {
// 模拟制作汉堡需要2秒钟
setTimeout(() => {
const 成功 = true; // 假设这次制作成功了
if (成功) {
resolve('香喷喷的巨无霸汉堡'); // 承诺达成!传递汉堡给.then
} else {
reject('对不起,牛肉卖完了'); // 承诺失败!传递错误信息给.catch
}
}, 2000);
});
console.log(做汉堡); // 立即打印,此时状态是 pending
五、如何使用 Promise?—— then、catch、finally
我们拿到“小票”(Promise对象)后,如何知道它成功还是失败,并拿到结果呢?
1. .then() - 处理成功情况
当 Promise 的状态变为 fulfilled 时,.then() 里的回调函数会被调用。
做汉堡.then((得到的汉堡) => {
console.log(`太好了!我吃到了${得到的汉堡}`);
});
// 2秒后输出:太好了!我吃到了香喷喷的巨无霸汉堡
2. .catch() - 处理失败情况
当 Promise 的状态变为 rejected 时,.catch() 里的回调函数会被调用。这是处理错误推荐的方式。
// 修改上面例子,让 const 成功 = false;
做汉堡
.then((得到的汉堡) => {
console.log(`太好了!我吃到了${得到的汉堡}`);
})
.catch((错误原因) => {
console.log(`非常失望!原因:${错误原因}`);
});
// 2秒后输出:非常失望!原因:对不起,牛肉卖完了
3. .finally() - 无论成功失败都会执行
类似于 try...catch 中的 finally,用于执行无论成功还是失败都需要做的清理工作。
做汉堡
.then((汉堡) => { /* ... */ })
.catch((错误) => { /* ... */ })
.finally(() => {
console.log('无论有没有吃到汉堡,我都要离开柜台了');
});
接下来,用整合一下Promise的代码用外卖示例下。
// 1. 点外卖这个动作(创建一个Promise)
const orderFood = new Promise((resolve, reject) => {
// 模拟一个异步操作,比如厨房做饭,需要3秒钟
console.log("厨师开始做饭...");
setTimeout(() => {
const isSuccess = Math.random() > 0.3; // 70%的概率成功
if (isSuccess) {
// 成功啦!外卖做好了!
const meal = "香喷喷的红烧肉";
resolve(meal); // 将Promise状态从pending变为fulfilled,并传递结果
} else {
// 失败啦!没菜了!
const error = new Error("抱歉,红烧肉卖完了");
reject(error); // 将Promise状态从pending变为rejected,并传递错误原因
}
}, 3000);
});
// 2. 使用这张“小票”(使用Promise)
console.log("我拿到了小票,可以去刷手机了...");
orderFood
.then((result) => {
// 成功回调(.then)
console.log("太棒了!我的:" + result);
})
.catch((error) => {
// 失败回调(.catch)
console.log("唉!" + error.message);
})
.finally(() => {
// 无论成功失败都会执行(.finally)
console.log("用餐结束,小票扔了");
});
运行结果可能有两种:
-
成功:
厨师开始做饭... 我拿到了小票,可以去刷手机了... (等待3秒...) 太棒了!我的:香喷喷的红烧肉 用餐结束,小票扔了
-
失败:
厨师开始做饭... 我拿到了小票,可以去刷手机了... (等待3秒...) 唉!抱歉,红烧肉卖完了 用餐结束,小票扔了
人生就是起起落落,不是100%成功,总会有失败,保持好心态。
六、Promise 如何解决“回调地狱”?—— 链式调用(Chaining)
这是 Promise 最强大的特性!.then() 方法总是返回一个新的 Promise,所以你可以继续在后面调用 .then(),形成链式调用。
让我们用 Promise 重写最开始的做饭例子:
// 假设 烧水、煮面、吃面 这些函数都返回一个 Promise
烧水()
.then((水) => {
console.log('水烧开了!');
return 煮面(水); // 关键!返回一个新的Promise给下一个.then
})
.then((面) => {
console.log('面煮好了!');
return 吃面(面); // 继续返回一个新的Promise
})
.then(() => {
console.log('终于吃完了!');
})
.catch((错误) => {
// 只需要一个.catch,就能捕获整个链条中任何一步发生的错误!
console.log('做饭失败了:', 错误);
});
看!代码变成了从上到下的顺序执行,而不是向右嵌套的“金字塔”,可读性大大增强!
链式调用的秘诀:在 .then() 的回调函数中:
-
如果你返回一个值(比如
return ‘abc’),它会被包装成一个成功的 Promise,传递给下一个.then。 -
如果你返回一个 Promise(比如
return 煮面(水)),下一个.then将等待这个 Promise 解决,并获得它的结果。 -
如果你抛出错误(比如
throw new Error(‘错了’)),它会触发链中的.catch。
其他常用方法
-
Promise.all([promise1, promise2, ...]) -
场景:等所有图片都下载完了再一起操作。
-
结果:只有所有Promise都成功,它才成功(返回一个结果数组);如果有一个失败,整个就失败。
Promise.all([下载图片1, 下载图片2, 下载图片3]) .then((图片数组) => { console.log('所有图片下载完成:', 图片Array); }) .catch((错误) => { console.log('有张图片下载失败了', 错误); });
-
Promise.race([promise1, promise2, ...])-
场景:多家请求,谁先到吃谁家的。
-
结果:哪个Promise最先改变状态(无论是成功还是失败),它就采用哪个的结果。
const 数据请求 = fetch('/api/data'); const 超时 = new Promise((resolve, reject) => { setTimeout(() => reject(new Error('请求超时')), 5000); });
Promise.race([数据请求, 超时]) .then((数据) => console.log('数据获取成功', 数据)) .catch((错误) => console.log('错误', 错误)); // 如果5秒内请求没完成,这里会触发超时错误
-
总结
特性
解释
优点
本质
一个代表异步操作最终结果的对象
将异步操作标准化为对象来处理
状态
pending(进行中)、fulfilled(已成功)、rejected(已失败)
状态不可逆,结果明确
方法
.then() 处理成功,.catch() 处理失败,.finally() 最终处理
错误处理更集中、方便
链式调用
.then() 返回新 Promise,可以连续调用
完美解决回调地狱,代码变为扁平结构
静态方法
Promise.all()(等待所有),Promise.race()(竞速)
处理复杂异步流程控制
Promise 是现代 JavaScript 异步编程的基石,是学习 async/await(一种更简洁的异步语法,其底层就是 Promise)的必备基础。希望今天这个讲解对你有帮助!