JavaScript Promises
TLDR: Promise 可以先拿到“还没到的结果”,再约好:结果到了之后要做什么。
一个非典型的 Promise
姑且不论晦涩的概念,把 Promise 想象成这样一件事:
你点了外卖,商家给了你一个订单号。
食物还没到,但你已经可以:
- 关注它的状态(正在制作 / 配送中 / 已送达)
- 约好送到时要做什么(比如开吃😋)
在 JavaScript 里,Promise 就是用来描述这种 未来某个时间点才会得到的值。
先看一个“非典型”、但足够简单的例子:
var p = new Promise(function (resolve, reject) {
// 这里直接给出结果,没有任何异步操作
resolve(42);
});
p.then(function (value) {
console.log("结果是:", value); // 结果是: 42
});
这里发生了什么?
new Promise(...)里传入的函数会立刻执行一次;- 我们在里面直接调用
resolve(42),表示:这个 Promise 成功完成了,结果是 42; -
后面的
.then(...)则是在说:“当结果准备好时,请帮我执行这段回调,并把结果传进来。”
从状态的角度看,一个 Promise 大概会经历三种状态:
pending:还在路上(外卖制作/配送中)fulfilled:成功了,有结果了(顺利吃上饭)rejected:失败了,有原因(骑手迷路 / 系统炸了)
在上面的例子里,因为我们立刻调用了 resolve(42),所以 Promise 会立即变成 fulfilled,然后执行 .then 里的回调。
这个例子看起来有点“假异步”——没有真正的网络请求、定时器之类,但它把 Promise 的核心节奏已经演示出来了:先定义,然后约定成功/失败之后要做什么。
一个更有趣的例子
真正用 Promise 的时候,通常都离不开“等一等”。比如我们用 setTimeout 模拟一个异步操作:
function wait(ms) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(ms);
}, ms);
});
}
wait(1000).then(function (time) {
console.log("等了 " + time + " ms");
});
这段代码在做什么?
wait(ms)返回一个 Promise 对象;- Promise 里面用
setTimeout模拟了一个异步任务; -
ms毫秒后,我们调用resolve(ms),表示:“好啦,我等完了,结果就是刚才传进来的那个毫秒数。”
于是,当我们写:
wait(1000).then(function (time) {
console.log("等了 " + time + " ms");
});
相当于告诉 JavaScript 引擎:
“帮我等 1 秒,等完之后就打印一行日志。”
因为 Promise 支持链式调用,我们可以把多个异步操作串起来写,让逻辑看起来顺顺溜溜的:
wait(500)
.then(function (t) {
console.log("Step 1:等了 " + t + " ms");
return wait(1000); // 再等 1 秒
})
.then(function (t) {
console.log("Step 2:又等了 " + t + " ms");
return "全部完成";
})
.then(function (msg) {
console.log(msg);
})
.catch(function (err) {
console.error("出错了:", err);
});
这里有几个关键点:
- 每个
.then都可以返回一个新的值或者返回另一个 Promise;- 返回普通值:下一个
.then直接拿到这个值; - 返回 Promise:下一个
.then会等这个 Promise 完成。
- 返回普通值:下一个
.catch可以统一处理前面链条里的异常:- 无论是
reject(...),还是回调里抛出的错误,都会一路冒泡到.catch。
- 无论是
你可以把这一整串理解成:
“先做 A,做完再做 B,做完再做 C,如果中途翻车,就走统一的错误处理通道。”
相对于一层套一层的回调(callback hell),Promise 至少让这件事长得像一条线,而不是一棵树。
小结:为什么要在意 Promise?
现代 JavaScript 里,只要涉及异步,就绕不开 Promise。 简单总结一下,它的价值主要有这几条:
- 把“未来的值”包装成一个对象
- 不再是“给我一个回调函数,我到时候会调用你”,
- 而是“给你一个 Promise,你可以在方便的时候再去关心它”。
- 让异步流程变得可读、可组合
- 用
.then链接步骤,用.catch统一处理错误; - 比一层套一层的回调要直观得多。
- 用
- 是 async/await 的基础
async/await本质上就是把 Promise 写得更像同步代码;- 但底层仍然是 Promise 在跑。
- 减少“意外行为”
- Promise 一旦从
pending变成fulfilled或rejected,状态就不会再变; - 比随意乱调回调函数可靠得多,更容易分析和调试。
- Promise 一旦从
下次当你看到:
- 某个 API 返回了一个
Promise; - 代码里出现一串
.then(...).then(...).catch(...); - 或者你在用
async/await写异步逻辑;
都可以想起本文开头那句话:
Promise 就是那个“外卖订单号”—— 结果还没到手,但你已经提前约好了,到了之后要做什么。