Promise 学习笔记 | 从小白到入门
这是一篇学习笔记,记录了我从回调地狱到 Promise 的学习过程。
一、先搞懂异步
JS 默认是单线程执行的。
啥意思?就是 JS 一次只能干一件事。
那遇到耗时的事咋办?比如向后端请求数据,要等 1 秒——
let a = 1;
setTimeout(() => {
a = 2;
}, 1000);
console.log(a); // 输出多少?
答案是 1,不是 2。
因为 setTimeout 是个异步任务,JS 碰到它,不会傻等着,而是先挂起来,继续往下执行。1 秒后回调才执行,把 a 改成 2。
同步:按顺序往下走 异步:碰到耗时任务,先挂起,继续干别的,等耗时任务完了再回来处理
二、回调地狱——异步的痛
看这个场景:
let a = null;
function A() {
// 向后端请求数据,耗时 1s
setTimeout(() => {
a = 100;
}, 1000);
}
function B() {
// 把拿到的数据渲染到页面上
console.log(a);
}
A();
B(); // 输出 null,因为 A 还没执行完呢
明明先调了 A,但 B 拿不到 A 的结果。因为 A 是异步的。
咋解决?回调——把 B 放到 A 里面:
function A(callback) {
setTimeout(() => {
a = 100;
callback(); // A 执行完了再调 B
}, 1000);
}
A(B);
这样能解决问题。但一旦逻辑复杂了,回调套回调,就成了 回调地狱:
getUser(function(user) {
getOrders(user.id, function(orders) {
getLogistics(orders[0].id, function(logistics) {
console.log(logistics);
});
});
});
三层还好,十层八层的根本没法维护。
三、Promise 是什么
Promise 就是来解决回调地狱的。
官方说法:Promise 是异步编程的一种解决方案。
我的理解:Promise 就像一个中间人。
举个栗子🌰:
小明想追女神(这就是一个异步任务,不知道结果),他找了红娘。
红娘给了他一个承诺(Promise):
- ✅ 女神同意了 → 红娘通知小明(成功)
- ❌ 女神拒绝了 → 红娘通知小明(失败)
小明不用天天干等着,该干嘛干嘛,有了结果红娘会通知他。
四、基本用法
function 追女生() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("felix love her");
resolve(); // 成功了!
}, 2000);
});
}
new Promise 创建一个 Promise 对象,它接受一个函数,这个函数有两个参数:
resolve:成功了调它reject:失败了调它
Promise 内部有个状态 state,一开始是 pending(等待中):
- 调了
resolve→ 变成resolved(成功) - 调了
reject→ 变成rejected(失败) - 状态一旦变了,就不能再改
五、then() 和 catch()
怎么拿到 Promise 的结果?
追女生()
.then(() => {
console.log("在一起了!");
})
.catch(() => {
console.log("被拒绝了...");
});
.then()→ 成功了执行.catch()→ 失败了执行
六、链式调用——告别回调地狱
Promise 最牛的地方:.then() 返回的也是一个 Promise,所以可以一直点下去。
看我的笔记里写的——追爱三部曲:
function xq() {
// 追爱,耗时 2s
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("felix love her");
resolve();
}, 2000);
});
}
function marry() {
// 结婚,耗时 1s
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("married successfully");
resolve();
}, 1000);
});
}
function baby() {
console.log("baby is born");
}
// 链式调用:追到→结婚→生娃
xq()
.then(() => {
return marry();
})
.then(() => {
return baby();
})
.catch(() => {
console.log("pity"); // 中间任何一步失败,都跳到这里
});
对比回调地狱,Promise 的写法:
- ✅ 扁平化了,从上往下读
- ✅ 错误统一在
.catch()处理 - ✅ 加步骤直接加
.then()就行
七、图解 Promise 内部原理
我自己写了个简化版,帮你理解 Promise 到底干了啥:
function Promise(fn) {
this.state = 'pending' // 一开始是准备状态
this.arr = [foo] // 存回调函数
const resolve = (res) => {
this.state = 'resolved' // 变成成功状态
// 然后执行之前存好的回调
// foo(res)
}
const reject = () => {
// 类似,变成失败状态
}
fn(resolve, reject) // 执行传进来的函数
}
new Promise((resolve, reject) => {
resolve()
})
执行流程:
new Promise()→ 得到一个状态为pending的对象.then(foo)→ 把foo存起来(还没执行)- 时间到了,
resolve被触发 - Promise 内部状态改为
resolved - 把之前存的
foo拿出来执行
就这么简单。Promise 本质上就是个状态机 + 回调队列。
八、Promise.all 和 Promise.race
Promise.all
等所有 Promise 都完成了再干活。
// 三个不相关的接口,一起请求
const p1 = fetch('/api/user');
const p2 = fetch('/api/news');
const p3 = fetch('/api/products');
Promise.all([p1, p2, p3]).then(([user, news, products]) => {
// 三个都拿到了,再一起渲染页面
});
一个失败,全部失败。
Promise.race
谁先完成就用谁的。
// 请求超时处理
Promise.race([
fetch('/api/data'),
new Promise((_, reject) => setTimeout(() => reject('超时了'), 3000))
]);
九、面试常问
输出顺序题
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
答案是:1, 4, 3, 2
原因是 JS 的事件循环:
- 同步代码先执行(1 和 4)
- 微任务(Promise 的 then)比宏任务(setTimeout)先执行
- 所以 3 在 2 前面
Promise 状态能变几次?
一次。从 pending 变成 resolved 或 rejected 后,就定死了,不能改。
总结
- 异步任务用回调解决,但多了就成了回调地狱
- Promise 让异步变扁平,链式调用
.then() - Promise 三种状态:pending → resolved / rejected,只能变一次
.then()存回调,resolve()触发回调- 学完 Promise 下一步学
async/await,写起来更像同步代码