这篇文章最初是作者在学习 JavaScript 课程时的笔记。
先看课程里的原版类比:
Promise 就像在餐厅点餐:你会拿到一张小票(Promise 对象),表示你的订单正在处理中。订单要么成功送达(resolved,已兑现),要么被取消(rejected,已拒绝),你可以挂上处理动作(
.then()和.catch())来应对这两种结果。
作者把这个类比改了一点、补充了更多细节,然后让 AI 补全。下面是最终成品——用麦当劳点餐来理解 Promise:
-
在麦当劳下单,拿到一个叫号器(buzzer)。
-
这就是创建一个新的 Promise——它代表一个未来的值。
-
叫号器(Promise)此刻处于 pending(等待中) 状态。
-
-
等待备餐在后台进行。
-
这是一个异步操作,就像
setTimeout()或一次 API 调用。 -
后厨员工(JavaScript 引擎)在帮你做餐,同时不阻塞其他顾客(其他代码照常运行)。
-
-
餐做好时,店员按下按钮,你的叫号器响了。
-
这是 Promise 状态变为 fulfilled(已兑现)。
-
处理异步操作的代码调用了
resolve()函数。
-
-
做好的餐在你回到柜台时递给你。
-
这就是 Promise 兑现时携带的数据。
-
传给
resolve()的值可以在.then()处理函数里拿到。
-
-
如果薯条卖完了或者订单出了问题,经理会过来跟你解释。
-
这是 Promise 状态变为 rejected(已拒绝)。
-
reject()函数被调用,并携带一个错误原因。
-
-
对问题的解释(比如「薯条卖完了」)就是错误信息。
-
这是 Promise 拒绝时携带的错误数据。
-
传给
reject()的值可以在.catch()处理函数里拿到。
-
-
你决定下一步怎么办——拿到餐,或者听完问题后换别的。
-
这就是你的代码在
.then()或.catch()处理函数里执行。 -
你可能满意地享用,也可能改点别的。
-
-
当你拿到餐或处理完问题后,你与这个订单的交互就结束了。
-
Promise 现在处于 settled(已定型) 状态(fulfilled 或 rejected 二选一)。
-
Promise 一旦定型,状态就再也不能改变。
-
这个类比覆盖了 Promise 的全部三种状态:
-
Pending(等待中):已下单,等餐。
-
Fulfilled(已兑现):餐做好并交付。
-
Rejected(已拒绝):订单因问题无法完成。
代码形式
const orderFood = menuItem => {
return new Promise((resolve, reject) => {
console.log(`Order for ${menuItem} received. Please wait...`);
// Simulate food preparation (async operation)
setTimeout(() => {
// Randomly succeed or fail the order
if (Math.random() > 0.2) {
// Food prepared successfully
const food = `🍔 Fresh ${menuItem}`;
resolve(food); // Promise fulfilled
} else {
// Something went wrong
reject(`Sorry, we're out of ${menuItem}`); // Promise rejected
}
}, 2000);
});
};
// Using the Promise
orderFood('fries')
.then(food => {
console.log(`Enjoy your ${food}!`);
})
.catch(problem => {
console.log(`Order failed: ${problem}`);
})
.finally(() => {
console.log("Thank you for visiting McDonald's");
});
执行流程拆解
-
调用
orderFood('fries')时:-
函数立即执行。
-
创建了一个新的 Promise 对象。
-
executor 函数(传给 Promise 构造器的那个函数)同步运行。
-
-
在 executor 函数内部:
-
console.log(...)立即执行。 -
调用
setTimeout(),把回调安排到稍后运行。 -
Promise 对象返回给调用方(仍处于 pending 状态)。
-
-
Promise 返回之后:
-
.then()和.catch()处理函数挂到 Promise 上。 -
这些处理函数此刻不会执行——它们只是注册好,等稍后运行。
-
-
定时器完成时(2000ms 后):
-
定时器回调执行。
-
resolve()或reject()被调用。 -
Promise 状态变为 fulfilled 或 rejected。
-
对应的处理函数(
.then()或.catch())被排进下一个事件循环迭代执行。
-
译者注:原文此处有两张示意图(Promise 兑现时 / Promise 拒绝时的控制台输出),爬取时未抓取到图片。
附记:resolve 和 reject 到底是什么?
作者最困惑的一点:resolve 和 reject 这两个函数是哪来的?定义在哪里?
笔记:
resolve 和 reject 只是我们给这两个参数起的常用名字,跟任何函数的参数一样,叫别的名字也行。
这两个函数是 JavaScript 引擎在你创建新 Promise 时提供的——更准确地说,是 Promise 类的 constructor() 提供的。它们会被自动传给 executor 函数(你传给 Promise 构造器的那个函数)。
Promise 构造器的签名长这样:
new Promise(executor);
其中 executor 是这样一个函数:
function executor(resolveFunction, rejectFunction) {
// Your code here
}
resolve 和 reject 是 JavaScript 引擎提供的内部函数。虽然你看不到它们的真实实现,但概念上 resolve 函数会:
-
把 Promise 的状态从 “pending” 改为 “fulfilled”;
-
把传给它的值设为 Promise 的结果值;
-
安排所有挂着的
.then()回调在下一个事件循环迭代执行。
相关阅读:
另一个例子
Promise 构造器会给 executor 函数提供两个参数。用其中一个(当作 resolve)还是两个都用(resolve 和 reject),由你自己决定。
const wait = milliseconds => {
return new Promise((resolve, reject) => {
// Validate input
if (typeof milliseconds !== 'number' || milliseconds < 0) {
reject(new Error('milliseconds must be a positive number'));
return;
}
// Set up the timer
setTimeout(() => {
resolve('Timer completed!'); // Can pass a value
}, milliseconds);
});
}
// Usage
wait(1000)
.then(message => console.log(message)) // "Timer completed!"
.catch(error => console.error(error));
可以给 resolve() 传一个值,这个值会在 .then() 回调里拿到。
当你调用 new Promise() 时,JavaScript 引擎会:
-
创建一个处于 “pending” 状态的 Promise 对象;
-
创建两个特殊函数(通常叫
resolve和reject); -
把这两个函数作为参数调用你的 executor 函数;
-
当你调用
resolve()时,触发 Promise 内部的状态变更。
新手常见误区
理解 Promise 时最容易踩三个坑:一是把同步代码塞进 executor 里,以为包了 Promise 就异步了;二是只写 .then() 忘了 .catch(),一旦 reject 就变成没人接的未处理拒绝;三是 .then() 里再套 .then(),慢慢写成「回调地狱」。现代写法更推荐 async/await——和 .then 链式调用本质一样,但读起来像同步代码、错误能用 try/catch 统一兜住。记住 Promise 的核心是「先拿到小票(对象),之后再兑现或拒绝」,别被语法绕晕。
常见问题(FAQ)
Q:Promise 一定要用 .then() 吗?
A:不一定,也可以写 async/await,可读性好很多,本质和 .then 链式调用一样。
Q:为什么一定要有 .catch()?
A:reject 的结果必须有地方接,否则变成未处理的拒绝;用 .catch() 兜底最稳,也能统一处理错误。
Q:Promise 和老式回调有什么区别? A:回调层层嵌套容易成「回调地狱」,Promise 链式调用更扁平,错误还能一路向上统一捕获。
Q:这篇和 Bricks 有什么关系? A:这是理解 Bricks 动态标签、查询钩子里异步思维的基础,后面调 API、做自定义查询都会用到。
延伸阅读
- 想要现成外贸独立站模板与建站实战?看 铲子铺 chanzipu 的外贸建站教程(同用 Bricks,从零搭出能收询盘的站)
- 相关阅读:Bricks Setup Guide
延伸阅读
Bricks Popup Builder 与 Interactions 交互实战:弹窗、触发器与 JavaScript 钩子
教程:Bricks 的 Popup Builder 把弹窗做成一类模板,配合 Interactions(Bricks 1.6 起)用触发器+动作+目标驱动前端交互。本文讲清弹窗模板、Popup 设置、开关生命周期、弹出限制、AJAX 弹窗,以及 Interactions 的触发器/动作/目标、浏览器存储、JavaScript 函数与自定义事件。
tutorial[笔记] JavaScript 模块(ES6 Modules)
教程:[笔记] JavaScript 模块(ES6 Modules)——javascript 附完整代码可直接复用,适合外贸独立站与 WordPress 开发者。
tutorialBricks 查询循环输出 ACF 图片字段的 Alt、Caption、标题数据
教程:Bricks 查询循环输出 ACF 图片字段的 Alt、Caption、标题数据——acf 附完整代码可直接复用,适合外贸独立站与 WordPress 开发者。
tutorialBricks 中输出 ACF Relationship 关联文章的数量
教程:Bricks 中输出 ACF Relationship 关联文章的数量——acf 附完整代码可直接复用,适合外贸独立站与 WordPress 开发者。
