第3章 3.2 Promise 基础
前置章节回顾
上一章我们被「回调地狱」折磨得不轻——层层嵌套的回调函数让代码变成了一团乱麻,改一行代码都要找半天。你还记得那个「查天气 → 查衣服 → 查出门建议」的噩梦吗?
本章目标
这一章,我们要学一个新武器:Promise。它能把「层层嵌套」的回调拍平,变成「链式书写」,让代码从右往左金字塔变成从左往右流水线。学完本文,你就能自己写出「等数据来了再处理」的逻辑,而且代码整整洁洁。
配图说明:回调地狱 vs Promise 链式调用对比图 
🎯 开场 3 分钟:为什么要学这个?
场景引入:等外卖的焦虑
想象你点了外卖,APP 上显示「正在等待骑手接单」,你每隔 10 秒就打开 APP 看一次——累不累?
后来 APP 出了个功能:「接单后自动通知你」,你就可以安心刷视频了,等通知来了再去拿。
回调函数就像你不停地打开 APP 刷新,Promise 就像那个「自动通知」功能。
现实开发中的痛点
做 Web 开发时,你肯定遇到过这种代码:
// 读取用户信息
getUser(userId, function(user) {
// 根据用户查订单
getOrders(user.id, function(orders) {
// 根据订单查商品详情
getProducts(orders, function(products) {
// 根据商品查库存
getStock(products, function(stock) {
console.log('最终结果:', stock);
});
});
});
});
每一步都要等上一步完成才能开始,而且如果中途任何一步出错……恭喜你,debug 去吧。
学完本文能解决
- ✅ 把「金字塔」代码变成「流水线」代码
- ✅ 优雅地处理「成功」和「失败」两种情况
- ✅ 学会 Promise 的链式调用,让代码易读易维护
🧱 基础 25 分钟:核心概念
3.2.1 Promise 是什么?
生活类比:Promise就像餐厅的「取餐号牌」。
你点完餐,服务员给你一个号牌,告诉你「做好了会响,你不用在这干等着」。你拿到号牌后可以玩手机、聊天,等号牌响了去取餐就行。
- 号牌本身:就是 Promise 对象
- 「做好了」:就是 Promise 变成
fulfilled状态(成功) - 「没货了」:就是 Promise 变成
rejected状态(失败)
3.2.2 为什么用 Promise?
解决啥痛点:回调函数的问题在于「不确定性」——你不知道上一步什么时候完成,只能被动等待或者层层嵌套。
Promise 提供了「约定」:我保证最终会给你一个结果(成功或失败),你只需要 .then() 注册一下怎么处理。
配图说明:Promise 三种状态流转图 
3.2.3 怎么用?最小代码演示
第一步:创建一个 Promise
// 创建 Promise,接收一个函数作为参数
// 这个函数有两个参数:resolve 和 reject
const myFirstPromise = new Promise(function(resolve, reject) {
// 模拟一个异步操作(比如读文件、网络请求)
setTimeout(function() {
const 成功 = true;
if (成功) {
resolve('数据加载完成!'); // 成功了,调用 resolve
} else {
reject('网络错误'); // 失败了,调用 reject
}
}, 1000); // 1秒后执行
});
console.log('我不需要等,直接执行到这里');
输出:
我不需要等,直接执行到这里
(等待1秒后)
数据加载完成!
解释:Promise 创建后立即返回,不会阻塞后面代码。1秒后「异步操作」完成,才调用 resolve 或 reject。
第二步:处理结果(then / catch / finally)
myFirstPromise
.then(function(结果) {
console.log('成功啦:', 结果);
})
.catch(function(错误) {
console.log('失败啦:', 错误);
})
.finally(function() {
console.log('不管成功还是失败,我都会执行');
});
解释:
- .then() 注册「成功后怎么办」
- .catch() 注册「失败后怎么办」
- .finally() 注册「不管成功失败都执行啥」
第三步:resolve 和 reject 能传值
const 查成绩 = new Promise(function(resolve, reject) {
const 分数 = 85;
if (分数 >= 60) {
resolve({ success: true, score: 分数 });
} else {
reject({ success: false, reason: '没及格' });
}
});
查成绩
.then(function(data) {
console.log('成绩单:', data); // { success: true, score: 85 }
})
.catch(function(data) {
console.log('挂了:', data); // 如果没及格就执行这里
});
解释:resolve 和 reject 可以传任意值,这个值会被 .then() 或 .catch() 收到。
3.2.4 链式调用——Promise 的精髓
Promise 最厉害的地方:.then() 返回的还是 Promise,可以继续 .then()。
// 模拟:步骤1:登录 -> 步骤2:获取用户信息 -> 步骤3:获取用户权限
const 登录 = function(用户名) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve({ userId: 123, name: 用户名 });
}, 500);
});
};
const 获取权限 = function(用户ID) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(['read', 'write', 'delete']);
}, 500);
});
};
// 链式调用:登录成功后获取用户信息,获取成功后获取权限
登录('小明')
.then(function(用户) {
console.log('登录成功,用户信息:', 用户);
return 获取权限(用户.userId); // 返回新的 Promise
})
.then(function(权限列表) {
console.log('权限列表:', 权限列表);
});
输出:
登录成功,用户信息:{ userId: 123, name: '小明' }
权限列表:['read', 'write', 'delete']
解释:每个 .then() 里 return 一个值或 Promise,下一个 .then() 就能接收到。这就是「流水线」的威力。
3.2.5 Promise.all——并行执行,一次性拿到所有结果
场景:页面加载时要同时查「用户信息」「订单列表」「推荐商品」,三个请求互不相干,等三个都完成了再一起处理。
const 查用户 = new Promise(function(resolve) {
setTimeout(function() { resolve({ name: '小明', age: 18 }); }, 500);
});
const 查订单 = new Promise(function(resolve) {
setTimeout(function() { resolve(['订单A', '订单B']); }, 800);
});
const 查推荐 = new Promise(function(resolve) {
setTimeout(function() { resolve(['商品1', '商品2']); }, 600);
});
// Promise.all:所有 Promise 都成功,才算成功
Promise.all([查用户, 查订单, 查推荐])
.then(function(结果列表) {
console.log('用户:', 结果列表[0]);
console.log('订单:', 结果列表[1]);
console.log('推荐:', 结果列表[2]);
})
.catch(function(错误) {
console.log('有一个请求失败了:', 错误);
});
输出:
用户:{ name: '小明', age: 18 }
订单:['订单A', '订单B']
推荐:['商品1', '商品2']
解释:Promise.all() 等待所有 Promise 完成,输出顺序和输入顺序一致。耗时 = 最慢那个(约800ms),而不是加起来。
🔥 实战 35 分钟:3 个递进小项目
项目 1(5分钟):计时器 Promise
需求:写一个「等 X 毫秒后自动完成」的 Promise 工具函数。
完整代码:
// 等待指定毫秒数后自动 resolve
function 等待(毫秒) {
return new Promise(function(resolve) {
setTimeout(resolve, 毫秒);
});
}
console.log('开始计时...');
等待(2000) // 等待2秒
.then(function() {
console.log('2秒到!');
return 等待(1000); // 再等1秒
})
.then(function() {
console.log('又过了1秒,总共3秒了');
});
预期输出:
开始计时...
(等待2秒)
2秒到!
(等待1秒)
又过了1秒,总共3秒了
一句话解释:等待() 函数返回一个 Promise,.then() 注册的回调会在指定时间后自动执行。
项目 2(15分钟):CSV 数据处理流水线
需求:从模拟的 CSV 数据中筛选「年龄大于18岁」的用户,并统计人数。
数据格式(模拟 CSV 字符串):
name,age,city
小明,15,北京
老王,25,上海
小红,17,广州
张三,30,深圳
李四,19,成都
完整代码:
// 模拟的 CSV 数据
const csv数据 = `name,age,city
小明,15,北京
老王,25,上海
小红,17,广州
张三,30,深圳
李四,19,成都`;
// 把 CSV 字符串解析成对象数组
function 解析CSV(csv字符串) {
return new Promise(function(resolve, reject) {
try {
const 行列表 = csv字符串.trim().split('\n');
const 表头 = 行列表[0].split(',');
const 数据 = 行列表.slice(1).map(function(行) {
const 值列表 = 行.split(',');
return {
name: 值列表[0],
age: parseInt(值列表[1]),
city: 值列表[2]
};
});
resolve(数据);
} catch (错误) {
reject('CSV解析失败:' + 错误.message);
}
});
}
// 筛选年龄大于指定值的数据
function 筛选年龄大于(数据, 最小年龄) {
return new Promise(function(resolve) {
const 结果 = 数据.filter(function(用户) {
return 用户.age > 最小年龄;
});
resolve(结果);
});
}
// 统计并输出结果
function 统计输出(数据) {
return new Promise(function(resolve) {
console.log('=== 筛选结果 ===');
console.log('符合条件的人数:', 数据.length);
数据.forEach(function(用户) {
console.log('- ' + 用户.name + ',' + 用户.age + '岁,住' + 用户.city);
});
resolve(数据.length);
});
}
// 执行流水线
解析CSV(csv数据)
.then(function(数据) {
console.log('解析成功,共' + 数据.length + '条记录');
return 筛选年龄大于(数据, 18); // 筛选18岁以上的
})
.then(function(筛选后数据) {
return 统计输出(筛选后数据);
})
.then(function(人数) {
console.log('=== 任务完成 ===');
})
.catch(function(错误) {
console.log('出错了:', 错误);
});
预期输出:
解析成功,共5条记录
=== 筛选结果 ===
符合条件的人数:3
- 老王,25岁,住上海
- 张三,30岁,住深圳
- 李四,19岁,住成都
=== 任务完成 ===
一句话解释:把 CSV 解析 → 数据筛选 → 结果输出串成一条流水线,每个步骤都是独立的 Promise,职责清晰。
项目 3(15分钟):待办事项管理器
需求:做一个命令行待办清单,支持「添加」「完成」「查看未完成」三个操作。
完整代码:
const readline = require('readline');
// 模拟数据库(内存存储)
const 数据库 = {
待办列表: [
{ id: 1, 内容: '买牛奶', 已完成: false },
{ id: 2, 内容: '写周报', 已完成: false },
{ id: 3, 内容: '健身', 已完成: true }
],
下一个ID: 4
};
// 模拟数据库操作的 Promise 封装
const 查询所有 = function() {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(数据库.待办列表);
}, 100);
});
};
const 添加 = function(内容) {
return new Promise(function(resolve) {
setTimeout(function() {
const 新任务 = {
id: 数据库.下一个ID++,
内容: 内容,
已完成: false
};
数据库.待办列表.push(新任务);
resolve(新任务);
}, 100);
});
};
const 标记完成 = function(任务ID) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
const 任务 = 数据库.待办列表.find(function(t) {
return t.id === 任务ID;
});
if (任务) {
任务.已完成 = true;
resolve(任务);
} else {
reject('任务ID不存在:' + 任务ID);
}
}, 100);
});
};
const 筛选未完成 = function() {
return new Promise(function(resolve) {
setTimeout(function() {
const 结果 = 数据库.待办列表.filter(function(t) {
return !t.已完成;
});
resolve(结果);
}, 100);
});
};
// 显示待办列表
function 显示列表(列表) {
console.log('\n========== 待办清单 ==========');
if (列表.length === 0) {
console.log('(空)');
} else {
列表.forEach(function(任务) {
const 状态 = 任务.已完成 ? '✓' : '○';
const 前缀 = 任务.已完成 ? '[完成]' : '[待办]';
console.log(前缀 + ' ' + 任务.id + '. ' + 任务.内容 + ' ' + 状态);
});
}
console.log('==============================\n');
}
// ========== 主程序 ==========
// 演示流程
console.log('欢迎使用待办清单管理器!\n');
// 1. 查看当前列表
查询所有()
.then(function(列表) {
显示列表(列表);
return 筛选未完成();
})
// 2. 查看未完成
.then(function(未完成列表) {
console.log('【未完成任务】');
显示列表(未完成列表);
return 添加('学习 Promise');
})
// 3. 添加新任务
.then(function(新任务) {
console.log('已添加:' + 新任务.内容 + ' (ID=' + 新任务.id + ')');
return 标记完成(1);
})
// 4. 标记任务1完成
.then(function(完成任务) {
console.log('已标记完成:' + 完成任务.内容);
return 筛选未完成();
})
// 5. 再次查看未完成
.then(function(未完成列表) {
console.log('\n【更新后的未完成任务】');
显示列表(未完成列表);
})
.catch(function(错误) {
console.log('操作失败:', 错误);
});
预期输出:
欢迎使用待办清单管理器!
========== 待办清单 ==========
[待办] 1. 买牛奶 ○
[完成] 3. 健身 ✓
[待办] 2. 写周报 ○
==============================
【未完成任务】
========== 待办清单 ==========
[待办] 1. 买牛奶 ○
[待办] 2. 写周报 ○
==============================
已添加:学习 Promise (ID=4)
已标记完成:买牛奶
【更新后的未完成任务】
========== 待办清单 ==========
[待办] 2. 写周报 ○
[待办] 4. 学习 Promise ○
==============================
一句话解释:每个数据库操作都封装成 Promise,主程序通过 .then() 链把操作串联起来,逻辑清晰。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:忘记 return Promise
// ❌ 错误示例
fetch('/api/user')
.then(function(user) {
fetch('/api/orders/' + user.id) // 忘记 return
})
.then(function(orders) {
console.log(orders); // orders 是 undefined!因为上一步没返回
});
// ✅ 正确示例
fetch('/api/user')
.then(function(user) {
return fetch('/api/orders/' + user.id); // 必须 return
})
.then(function(orders) {
console.log(orders);
});
解释:.then() 里的代码要传给下一个 .then(),必须 return。没 return 就是 undefined。
坑 2:在 .then() 里 throw 错误而不是 reject
// ❌ 错误示例
somePromise()
.then(function(data) {
if (!data) {
console.log('出错了'); // 只是打印,没有 reject
}
return data;
})
.catch(function() {
console.log('这里捕获不到上面的错误!');
});
// ✅ 正确示例
somePromise()
.then(function(data) {
if (!data) {
throw new Error('数据为空'); // 用 throw 触发 catch
}
return data;
})
.catch(function(错误) {
console.log('捕获到错误:', 错误.message);
});
解释:.catch() 只能捕获被 reject() 或 throw 抛出的错误。普通 console.log 不算。
坑 3:Promise.all 里有一个失败就全失败
// ❌ 错误示例
Promise.all([
fetch('/api/a'),
fetch('/api/b'),
fetch('/api/c')
]).then(function(结果) {
// 如果任何一个请求失败,这里不会执行
});
// ✅ 正确示例:全部 resolve 才算成功
// 如果需要「容忍部分失败」,用 Promise.allSettled
Promise.allSettled([
fetch('/api/a'),
fetch('/api/b'),
fetch('/api/c')
]).then(function(结果列表) {
结果列表.forEach(function(结果) {
if (结果.status === 'fulfilled') {
console.log('成功:', 结果.value);
} else {
console.log('失败:', 结果.reason);
}
});
});
解释:Promise.all 是「与」的关系,有一个失败整体就失败。Promise.allSettled 是「不管成功失败都返回」。
坑 4:回调里又用回调,没有真正用 Promise
// ❌ 错误示例:表面用 Promise,实际还是回调嵌套
function 假Promise(回调) {
setTimeout(function() {
if (Math.random() > 0.5) {
回调(null, '成功');
} else {
回调('失败', null);
}
}, 100);
}
// ✅ 正确示例:真正返回 Promise
function 真Promise() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (Math.random() > 0.5) {
resolve('成功');
} else {
reject('失败');
}
}, 100);
});
}
解释:有些老代码把回调函数叫「callback」,但并不是 Promise。Promise 一定是 new Promise() 返回的对象。
坑 5:忘记处理 reject
// ❌ 危险示例:没有任何错误处理
fetch('/api/data')
.then(function(data) {
console.log(data);
});
// 如果 fetch 失败,没有任何提示,静默失败
// ✅ 正确示例:至少要加 catch
fetch('/api/data')
.then(function(data) {
console.log(data);
})
.catch(function(错误) {
console.error('请求失败:', 错误);
});
解释:没有 .catch() 的 Promise 就像没有 try-catch 的代码,出了错都不知道。
性能小贴士:避免不必要的 await
// ❌ 低效:串行执行,耗时 = A + B + C
async function 串行获取() {
const a = await 获取A();
const b = await 获取B();
const c = await 获取C();
return [a, b, c];
}
// ✅ 高效:并行执行,耗时 = max(A, B, C)
async function 并行获取() {
const [a, b, c] = await Promise.all([
获取A(),
获取B(),
获取C()
]);
return [a, b, c];
}
解释:如果多个请求之间没有依赖,不要串行 await,用 Promise.all() 并行执行,速度快很多。
调试技巧:Promise 链里打日志
// 小技巧:每个 .then() 前后打日志,方便定位问题
fetch('/api/user')
.then(function(user) {
console.log('[调试] 获取用户成功:', user);
return fetch('/api/orders/' + user.id);
})
.then(function(orders) {
console.log('[调试] 获取订单成功:', orders.length, '条');
return 加工订单(orders);
})
.catch(function(错误) {
console.error('[调试] 发生错误:', 错误);
throw 错误; // 重新抛出,避免静默失败
})
.finally(function() {
console.log('[调试] 请求流程结束');
});
解释:在关键节点加 [调试] 日志,出问题时一眼就能看到卡在哪一步。
✏️ 练习题 + 作业题
练习题(5道,10分钟内完成)
练习 1(2分钟):修改等待时间
- 输入:把项目1的等待时间从 2000 改成 3000
- 预期输出:2秒变成3秒后才输出"2秒到!"
- 提示:等待(毫秒) 函数的参数就是毫秒数
练习 2(2分钟):添加年龄筛选条件
- 输入:把项目2的年龄筛选从 >18 改成 >20
- 预期输出:符合条件人数从3人变成2人(老王和张三)
- 提示:找一个 18 改成 20
练习 3(2分钟):筛选城市
- 输入:在项目2基础上,新增一个 筛选城市(数据, 城市名) 函数
- 预期输出:调用 筛选城市(数据, '北京') 只返回住在北京的用户
- 提示:参考 筛选年龄大于 的写法,把 .filter() 条件改成 用户.city === 城市名
练习 4(2分钟):串联两个筛选
- 输入:先筛选年龄>18,再筛选城市是"上海"
- 预期输出:只有"老王"符合条件
- 提示:.then() 链里 return 另一个筛选函数的结果
练习 5(2分钟):分析报错
- 输入:下面代码运行后会输出什么?
new Promise(function(resolve, reject) {
reject('出错了');
})
.then(function() { console.log('第一个then'); })
.catch(function(e) { console.log('捕获:' + e); })
.then(function() { console.log('恢复后then'); });
- 预期输出:
捕获:出错了然后恢复后then - 提示:
catch后面的.then()还会继续执行
作业题(30分钟-2小时)
作业:做一个「数据验证流水线」
- 需求描述:做一个命令行工具,输入用户注册信息,依次验证:用户名是否为空、密码长度是否>=6、两次密码是否一致
- 功能点:
1. 用 Promise 封装每个验证步骤
2. 验证失败时用reject()提示具体错误
3. 验证通过后输出「注册成功」 - 加分项:
1. 验证通过后用Promise.all()同时发送「发欢迎邮件」和「记录日志」两个模拟操作
2. 给每个步骤加[验证中][成功][失败]日志 - 验收标准:
- 能跑起来(Node.js 环境)
- 输入错误数据能提示具体哪一步错
- 输入正确数据能走完整个流程
- 代码有适当注释
- 提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
本章3个核心点
- ✅ Promise 是什么:「取餐号牌」,承诺最终给结果,分
pending/fulfilled/rejected三种状态 - ✅ .then() 链式调用:
return决定下一个.then()收到什么,实现流水线 - ✅ Promise.all():并行执行多个 Promise,等全部成功才继续
延伸学习资源
- MDN Promise 文档:权威、全面、中文友好
- 《你不知道的 JavaScript》上卷第3章:深入讲解 Promise 原理
- 视频:JavaScript Promise 慕课网免费教程(约1小时)
互动钩子
「你在实际项目里遇到过回调地狱吗?当时是怎么解决的?评论区聊聊,老粉优先回复!」
下章预告:Promise 链写起来已经很舒服了,但 async/await 让它更像「同步代码」。下一章我们学完就能把 .then().then().then() 变成看起来像「顺序执行」的神奇语法糖。

评论(0)