从回调地狱到手写 Promise:一次彻底搞懂 JavaScript 异步的底层逻辑
本文介绍了JavaScript中Promise的核心概念和使用方法。首先通过餐厅点餐场景生动解释了Promise的工作原理,包括pending、fulfilled和rejected三种状态。重点讲解了then链式调用的实现机制,catch和finally的本质,以及async/await作为Promise语法糖的作用。文章还提供了一个简化版Promise的手写实现,并介绍了Promise.all和
目录
Promise 静态方法(all / race / allSettled / any)
Promise 基础使用
在 ES6 之前,JavaScript 处理异步逻辑主要依赖回调函数。随着业务复杂度提升,回调嵌套容易形成“回调地狱”,代码可读性和可维护性都很差。
比如我们有以下一个场景
- 先获取用户信息
- 再根据用户信息获取订单列表
- 再根据订单获取物流信息
getUser(userId, function (user) {
getOrders(user.id, function (orders) {
getLogistics(orders[0].id, function (logistics) {
console.log("最终物流信息:", logistics);
}, function (err) {
console.error("获取物流失败:", err);
});
}, function (err) {
console.error("获取订单失败:", err);
});
}, function (err) {
console.error("获取用户失败:", err);
});
上述代码就存在层层嵌套,结构越来越深,可读性以及维护性差,因此为了解决这些问题,ES6 引入了 Promise。
用一个餐厅点餐场景去理解Promise
假设你走进一家餐厅,对服务员说:
“我要一份牛肉面。”
在这一刻,发生了几件事情:
你的订单被创建,你拿到了一张小票
这张“小票”,就相当于一个 Promise 对象。 此时你也处于等待状态。
然后你对服务员说,面做好了叫我,也就是你留了一个联系方式,也就是你定义.then()
之后厨房这边做好面了,就执行了resolve, 你的订单状态也就从pending 变成了 → fulfilled 表示你的牛肉面做好了
那么就会通知你说你的面做好了,那么你就可以执行.then() 。
const myPromise = new Promise((resolve) => {
console.log("1. 下单");
// 厨房开始做面
setTimeout(() => { //过了一段时间面做好了
console.log("3. 面做好了");
resolve(); // 执行resolve
}, 1000);
});
myPromise.then(() => {
console.log("4. 开吃!");
});
console.log("2. 等待中...");
promise定义
综合上述,我们现在正式给出一个promise的定义。
Promise 对象表示异步操作最终的完成(或失败)以及其结果值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。
也就是当我们执行一个异步操作时,它不会立即返回结果,而是返回一个 Promise 对象。等异步操作完成后,Promise 会把最终结果(成功或失败)交给我们处理。
Promise 的三种状态
一个 Promise 在生命周期中只会处于以下三种状态之一:
-
pending(待定)
初始状态,既没有成功,也没有失败。
-
fulfilled(已兑现)
表示异步操作成功完成,并得到一个结果。
-
rejected(已拒绝)
表示异步操作失败,并得到一个错误原因。
需要注意的是:
Promise 的状态一旦从 pending 变为 fulfilled 或 rejected,就不会再发生改变。
理解 then 链式调用
Promise 之所以能解决“回调地狱”,核心就在于:
then 会返回一个新的 Promise,从而形成链式调用。
then 为什么可以一直链下去?
每一次调用 then() 时:
- 这个.then()都会创建一个新的 Promise
- 新 Promise 的状态由当前 then 的返回值决定
看这段代码:
newPromise(resolve => {resolve(1);
})
.then(res => {// res = 1
return res +1;
})
.then(res => {// res = 2
console.log(res);
});
执行过程是:
- 第一个 Promise resolve(1)
- 第一个 then 收到 1
- 返回 2
- 这个 2 会被自动包装成一个新的 Promise
- 第二个 then 收到 2
这个返回普通值会发生什么?
.then(res => {
return 2;
})
等价于:
.then(res => {
return Promise.resolve(2);
})
也就是说:
then 返回普通值时,会被自动包装成一个 fulfilled 状态的 Promise。
这也是为什么链条可以持续下去。
如果返回的是 Promise 呢?
.then(res => {
return new Promise(resolve => {
setTimeout(() => {
resolve(3);
},1000);
});
})
这时候:
- 下一个 then 会等待这个 Promise 完成
- 完成后这个Promise后再继续执行
如果发生错误
Promise 不仅有 resolve,还有 reject。
当执行:
reject("失败");
或者:
thrownewError("出错了");
都会使当前 Promise 进入 rejected 状态。
这时:
- 后续的 then 会被跳过
- 会直接进入最近的 catch
例如:
newPromise((resolve, reject) => {reject("失败");
})
.then(res => {console.log("不会执行");
})
.catch(err => {console.log("进入 catch:", err);
});
输出:
进入catch: 失败,不会去执行中间那个 .then()
catch 本质是什么?
其实:
promise.catch(fn)
等价于:
promise.then(null, fn)
也就是说:
catch 只是 then 的语法糖,用来专门处理 rejected 状态。
finally 又是什么?
Promise 还有一个方法:
.finally()
它的特点是:
不管 Promise 成功还是失败,都会执行。
newPromise((resolve, reject) => {resolve("成功");
})
.then(res => {console.log(res);
})
.catch(err => {console.log(err);
})
.finally(() => {console.log("结束了");
});
输出:
成功
结束了
如果失败:
失败
结束了
完整结构模型
链式调用的本质结构其实是:
Promise1
↓then()
Promise2
↓then()
Promise3
↓catch()
Promise4
↓finally()
Promise5
每一次调用:
- 都会生成一个新的 Promise
- 状态会在链条中不断传递
理解 async/await 本质
目前,随着我们的.then太多了,我们又发现好像这个代码不太直观, 我们希望能更好控制数据流
getUser()
.then(user => getOrders(user.id))
.then(orders => getDetail(orders[0].id))
.then(detail => {
console.log(detail);
})
.catch(err => {
console.log(err);
});
因此,我们有了async
async function getData() {
try {
const user = await getUser();
const orders = await getOrders(user.id);
const detail = await getDetail(orders[0].id);
console.log(detail);
} catch (err) {
console.log(err);
}
}
定义
async/await 是 Promise 链式调用的语法糖。
async
首先,async是用来声明一个函数,让一个函数的返回结果永远返回Promise对象
async function test() {
return 1;
}
等价
function test() {
return Promise.resolve(1);
}
// 因此对于上面这个test()函数,我们知道是返回一个promise对象,就可以写
test().then(res => console.log(res));
await
await 本质是暂停当前函数,等待 Promise 完成。
- 它只暂停当前 async 函数
- 不会阻塞整个线程
await x
Promise.resolve(x).then(...)
其实是,await会把await后面的内容变成微任务处理,然后根据js的事件循环流程执行。比如以下,
async function test() {
console.log(1);
await Promise.resolve();
... // 这里内容以及下面的console.log(2) 这块代码块都会变成一个微任务 也就是加入微任务队列
console.log(2);
}
console.log(3);
test();
console.log(4);
print
3
1
4
2
手写简版 Promise
最后我们实现一个简单的Promise
class MyPromise {
constructor(executor) {
// Promise 初始状态必须是 pending
// 因为 Promise 一创建时还不知道结果
this.state = "pending";
// 成功后的值
this.value = undefined;
// 失败后的原因
this.reason = undefined;
// 用数组存储成功回调
// 因为当状态是 pending 时,then 可能会被多次调用
this.onFulfilledCallbacks = [];
// 用数组存储失败回调
this.onRejectedCallbacks = [];
// resolve 函数
const resolve = (value) => {
// 状态不可逆,只能从 pending 改一次
if (this.state === "pending") {
this.state = "fulfilled";
this.value = value;
// 状态改变后,执行之前存储的所有成功回调
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
// reject 函数
const reject = (reason) => {
if (this.state === "pending") {
this.state = "rejected";
this.reason = reason;
// 执行之前存储的失败回调
this.onRejectedCallbacks.forEach(fn => fn());
}
};
// !!!执行入口:executor 需要立即执行
// 因为 Promise 构造函数是同步执行的
try {
executor(resolve, reject);
} catch (error) {
reject(error); // 如果执行器报错,直接 reject
}
}
// 定义then
then(onFulfilled, onRejected) {
// 10️⃣ then 必须返回一个新的 Promise
// 这是链式调用的核心
return new MyPromise((resolve, reject) => {
if (this.state === "fulfilled") {
// then 必须异步执行(微任务)
queueMicrotask(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
});
}
if (this.state === "rejected") {
queueMicrotask(() => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
});
}
if (this.state === "pending") {
// 如果还是 pending,说明 resolve 还没执行
// 先把回调存起来
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
});
});
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
});
});
}
});
}
// catch 本质是 then(null, onRejected)
catch(onRejected) {
return this.then(null, onRejected);
}
// finally 不改变状态,只执行回调
finally(callback) {
return this.then(
value => {
callback();
return value;
},
reason => {
callback();
throw reason;
}
);
}
}
Promise 静态方法(all / race / allSettled / any)
Promise.all
当你有多个异步任务:
- 全部成功 → 才算成功
- 只要有一个失败 → 整体失败
Promise.all([
fetchUser(),
fetchOrders(),
fetchDetail()
]).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
// 全部 fulfilled 才 resolve
Promise.race()
多个异步任务:
- 谁先完成
- 用谁的结果
Promise.race([
fetchData(),
timeoutPromise()
]).then(res => {
console.log(res);
});
// 这个fetchData 与 timeoutPromise() 谁先执行完用谁的结果
其余静态方法可以查看文档
总结
Promise 的核心并不复杂:
-
Promise有三种状态
-
内部维护回调队列
-
状态不可逆
-
通过返回新 Promise 实现链式调用
async/await 也就是 Promise 的语法糖。
Reference
更多推荐


所有评论(0)