JavaScript 的秘密生活:Promise

发布: (2026年1月15日 GMT+8 13:06)
5 min read
原文: Dev.to

Source: Dev.to

Timothy 站在黑板前,手开始抽筋。他已经写了十分钟的代码,但实际上卡在了角落——字面意思。他的代码写得太靠右,以至于字母被压在木框边上。

“我只想做三件简单的事,”Timothy 抱怨道。“登录,获取用户的数据,然后获取他们的帖子。但代码看起来像通向无处的楼梯。”

他后退一步,展示他的怪物:

login(user, function(token) {
    getUser(token, function(profile) {
        getPosts(profile.id, function(posts) {
            console.log("Finally got posts:", posts);
        }, function(err) {
            console.error("Error getting posts");
        });
    }, function(err) {
        console.error("Error getting user");
    });
}, function(err) {
    console.error("Error logging in");
});

Margaret 走过来,手里拿着一块新的橡皮。“啊,”她说。“灾难金字塔。”

“这太难读了,”Timothy 说。“我在括号里迷路了。错误处理简直是噩梦。”

Margaret 把整段三角形代码擦掉。“那是因为你依赖了 callbacks。你把库的控制权交给别人,然后指望他们回调你。”

她在黑板上画了一个单一、整洁的框。

“是时候学习 the Promise 了。”

IOU(Promise 对象)

“Promise,” Margaret 解释道,“是一个对象。它不是值本身,而是一个占位符——一个欠条。”

Pending(待定): “我正在处理。”
Fulfilled(已完成): “这是你的数据。”
Rejected(已拒绝): “出错了。”

“与其把函数 传入 loginlogin 函数会把一个 Promise 对象 返回 给你。你可以持有它、传递它,并给它附加指令。”

const loginPromise = login(user);

loginPromise.then(function(token) {
    // This runs only when the promise is fulfilled
    return getUser(token);
});

“更好,” Timothy 承认道。“但如果我有三个步骤,我不是仍然要嵌套它们吗?”

“不,” Margaret 说。“因为 Promise 返回……另一个 Promise。你可以平铺链式调用。”

login(user)
    .then(token => getUser(token))
    .then(profile => getPosts(profile.id))
    .then(posts => console.log("Finally got posts:", posts))
    .catch(err => console.error("Something went wrong:", err));

Timothy 跟踪那行代码说:“它是平的。并且只有一个 .catch() 在最后吗?”

“是的,” Margaret 点头。“不必在每一层都处理错误,而是统一在底部捕获。金字塔结构消失了。”

暂停(Async / Await)

Timothy 看着链条。“它更简洁,”他同意道。“但它仍然看起来…不同。它不像普通的自上而下的编程。”

“你说得对,”Margaret 微笑着说。“这条链仍然有点‘语法化’。如果你想要看起来真正像人的代码,需要 async / await。”

她再次擦掉了白板。

“还记得 event loop 吗?还记得我们说过不能阻塞栈吗?”

“记得,”Timothy 背诵道。“如果我暂停栈,浏览器就会卡死。”

“正确。但 await 让你可以暂停 函数 而不阻塞 。”

async function showUserPosts() {
    try {
        const token = await login(user);
        const profile = await getUser(token);
        const posts = await getPosts(profile.id);

        console.log(posts);
    } catch (error) {
        console.error("Something went wrong:", error);
    }
}

Timothy 注视着它。“第 3 行:await login(user)。代码就在这里停下来?它在等网络吗?”

“是 函数 暂停,”Margaret 纠正道。“当引擎看到 await 时,它会挂起这个函数并实际上让位。它把控制权返回给浏览器的其余部分。”

她快速画了一个栈的示意图。

“当这个函数被挂起时,浏览器保持活跃——处理点击和渲染样式。栈是空闲的。”

“数据返回时会怎样?”

“函数的其余部分——续体——会被放入 microtask queue(VIP 队列)。只要栈为空,你的函数就会重新跳回去,在第 4 行继续执行,手里已经拿到数据。”

结论

Timothy 看着板上的三个版本:

  • The Pyramid (callbacks)
  • The Chain (promises)
  • The Story (async/await)

“他们都在做同样的事,” Timothy 意识到。

“是的,” Margaret 说。“但最后一个才是实情。它让你写出看起来是同步的代码——一步一步,只有一个 try/catch 块——却以异步的方式运行。”

她把粉笔递给了他。

“你不再需要嵌套你的逻辑了,Timothy。你只需要等它就行。”

Back to Blog

相关文章

阅读更多 »

中间件-完美-交响曲

GitHub 主页 https://github.com/hyperlane-dev/hyperlane 简介 Middleware 是 Web 开发中最强大的概念之一,也是最……

WebAssembly (Wasm) 简介

释放网页的内在极速怪兽:友好式深入 WebAssembly Wasm。有没有感觉你的网页浏览器虽然很棒,但有时却在与沉重的负载搏斗……