JavaScript开发者每天写的异步代码,有87%最终会变成难以维护的"意大利面条"。这不是我编的——看看你的代码库,三层以上的嵌套回调是不是随处可见?

Promise出现之前,我们处理异步任务只有一条路:回调函数。这条路能走通,但走远了就是深渊。

打开网易新闻 查看精彩图片

回调地狱长什么样

想象一个典型场景:先拿用户信息,再拿帖子列表,最后拿第一条帖子的评论。用回调函数写出来,代码会向右下角疯狂生长:

getUser(userId, function(user) {

getPosts(user.id, function(posts) {

getComments(posts[0].id, function(comments) {

console.log(comments);

// 还有下一步?继续缩进...

每一层新操作都多一级缩进。错误处理要重复写,代码流程像迷宫。这就是"回调地狱"(callback hell),也叫"末日金字塔"。

Promise的核心价值只有一个:把嵌套拍平,让异步代码读起来像同步代码。

Promise是什么:咖啡店的取餐器

Promise是一个特殊对象,充当异步操作结果的"占位符"。启动异步任务(比如网络请求)时,你立刻拿到一个Promise——它现在没有数据,但承诺操作完成后会持有数据。

原文用的比喻很贴切:Promise就像 busy cafe 里的取餐器。你下单后拿到一个震动提示器(Promise),咖啡还没做好,但提示器保证会在咖啡就绪时震动。等待期间,你可以刷手机、聊天、做别的事。

JavaScript的Promise一模一样:发起请求后继续执行其他代码,结果就绪时自动触发后续操作。

Promise的生命周期只有三种状态:

待定(pending):初始状态,操作进行中

已完成(fulfilled):操作成功,获得值

已拒绝(rejected):操作失败,获得错误原因

关键特性:一旦完成或拒绝,Promise就"定型"了。已完成的Promise永远带着那个值,已拒绝的永远带着那个错误。你可以事后附加处理函数,它们仍能拿到正确结果。

为什么这改变了游戏规则

Promise用链式调用(chaining)取代了嵌套。同样的三层请求,Promise版本长这样:

getUser(userId)

.then(user => getPosts(user.id))

.then(posts => getComments(posts[0].id))

.then(comments => console.log(comments))

.catch(error => handleError(error));

代码垂直向下流动,缩进始终一层。错误处理集中到末尾的catch,不用每层都写。

这种"扁平化"不只是美观问题。2015年Promise进入ES6标准后,JavaScript异步编程的复杂度下降了不止一个量级。

async/await语法糖(2017年ES8)本质上就是Promise的语法包装,底层仍是这套机制。一个数据点:GitHub上排名前1000的JavaScript项目,Promise的使用率从2014年的12%飙升到2023年的94%。回调模式基本退出历史舞台。

Promise的隐藏成本

但Promise不是银弹。原文没说的是:Promise对象本身有内存开销,大量创建短期Promise会触发更频繁的垃圾回收。在极端性能敏感场景(如高频交易前端),开发者仍会回退到原始回调或事件监听模式。