同学们好,我是 Eugene(尤金),一个拥有多年中后台开发经验的前端工程师~
(Eugene 发音很简单,/juːˈdʒiːn/,大家怎么顺口怎么叫就好)
你是否也有过:明明学过很多技术,一到关键时候却讲不出来、甚至写不出来?
你是否也曾怀疑自己,是不是太笨了,明明感觉会,却总差一口气?
就算想沉下心从头梳理,可工作那么忙,回家还要陪伴家人。
一天只有24小时,时间永远不够用,常常感到力不从心。
技术行业,本就是逆水行舟,不进则退。
如果你也有同样的困扰,别慌。
从现在开始,跟着我一起心态归零 ,利用碎片时间,来一次彻彻底底的基础扫盲。
这一次,我们一起慢慢来,扎扎实实变强。
不搞花里胡哨的理论堆砌,只分享看得懂、用得上的前端干货,
咱们一起稳步积累,真正摆脱"面向搜索引擎写代码"的尴尬。
1. 前言:Promise 到底解决什么问题?
简单粗暴一点的说,Promise 就是 JavaScript 中专门解决异步代码问题的工具,它最核心的作用,就是让原本混乱无序的异步代码,按照我们想要的顺序执行,彻底解决异步嵌套带来的可读性差、维护困难等问题,是前端异步编程的基础核心知识点。
2. 同步代码与异步代码:JavaScript 执行顺序解析
要学好 Promise,首先得分清什么是同步代码、什么是异步代码------这是理解 Promise 作用的前提,也是前端异步编程的基础。
- 同步代码:严格按顺序执行,就像排队办事,只有前一个人办完,下一个人才能开始,前一条代码执行完毕后,才会执行后一条代码,比如 1、2、3、4、5 依次执行,不会出现跳跃。
- 异步代码:可以不按顺序执行的代码,就像排队时有人临时去取东西,不用等他回来,后面的人可以继续办事,比如执行顺序可能是 2、1、3、4、6、5。常见的 JavaScript 异步场景有:网络请求、定时器、文件读写等。
我们直接上代码,直观感受异步代码的执行逻辑:
js
setTimeout(() => {
}, 1000)
↑这是一个 JavaScript 定时器,也是最常见的异步代码之一,它的作用是将包裹在方法体内的代码,延迟 1000 毫秒(1秒)后再执行。
3. 异步代码的特性:非阻塞与执行顺序验证
我们通过一个完整的定时器案例,验证异步代码的执行顺序,同时理解异步代码"非阻塞"的核心特性:
js
console.log('任务1', moment(new Date()).format('HH:mm:ss'))
setTimeout(() => {
console.log('任务2', moment(new Date()).format('HH:mm:ss'))
}, 1000)
// 补充说明:
// moment(new Date()).format('HH:mm:ss') 是用于输出当前执行时间的代码,
// 初学者如果不明白 moment 是什么、复制代码报错,可直接删除该部分,只输出 '任务1、任务2' 即可,不影响学习。
// 感兴趣的同学可自行搜索 moment.js 的使用方法,用于日期时间格式化。
执行结果如下(截图对应原文示例):

从截图可以清晰看到,任务2 比任务1 的打印时间晚了1秒,这就是异步代码的执行逻辑------定时器中的代码不会阻塞后续代码执行,而是延迟执行。
我们再调整一下代码顺序,进一步验证异步代码的特性:
js
setTimeout(() => {
console.log('任务2', moment(new Date()).format('HH:mm:ss'))
}, 1000)
console.log('任务1', moment(new Date()).format('HH:mm:ss'))
按照代码自上而下的顺序,很多初学者会认为:应该先等待1秒,输出任务2,再输出任务1。但实际执行结果并非如此,截图如下:

控制台输出显示:代码跳过了异步的任务2,先执行了同步的任务1,1秒后才执行任务2。这就证实了:定时器是典型的异步代码,不会按照自上而下的顺序执行,也不会阻塞后续同步代码的执行。
这里补充一个通俗类比,帮大家理解异步代码"非阻塞"的好处:
假设我们是交通参与者,开车行驶在路上,前方发生交通事故。如果有辅道可以绕行,我们肯定不会等到事故处理完再通行------这就是异步的逻辑,不会因为一个"任务"(事故处理)阻塞整体"流程"(通行)。如果必须等待事故处理完再通行,就会造成交通瘫痪,对应到代码中,就是"阻塞",会导致页面卡顿、响应缓慢。
4. 回调地狱:异步嵌套的痛点的详解
虽然异步代码是非阻塞的,但在实际开发中,我们经常会遇到"异步任务需要按顺序执行"的场景------这时候,单纯的异步嵌套就会产生"回调地狱",也是 Promise 要解决的核心痛点。
还是用交通场景类比:如果我们是交警,接到调度中心任务,先处理A路段事故,处理完再处理B路段事故(B任务必须等待A任务完成)。其他社会车辆可以正常绕行(不阻塞),但交警的两个任务必须同步执行。我们用代码还原这个场景:
js
console.log('社会车辆1')
console.log('社会车辆2')
setTimeout(() => {
console.log('交警处理事故1', moment(new Date()).format('HH:mm:ss'))
setTimeout(() => {
console.log('交警处理事故2', moment(new Date()).format('HH:mm:ss'))
}, 1000)
}, 1000)
console.log('社会车辆3')
console.log('社会车辆4')
执行结果截图:

从输出可以看到:社会车辆1、2、3、4(同步代码)先行执行,交警处理完事故1(第一个异步任务)后,才执行事故2(第二个异步任务),符合"异步任务按顺序执行"的需求。
但问题来了:如果需要处理的异步任务增多(比如6个事故),代码就会嵌套一层又一层,形成"回调地狱":
js
console.log('社会车辆1')
console.log('社会车辆2')
setTimeout(() => {
console.log('交警处理事故1', moment(new Date()).format('HH:mm:ss'))
setTimeout(() => {
console.log('交警处理事故2', moment(new Date()).format('HH:mm:ss'))
setTimeout(() => {
console.log('交警处理事故3', moment(new Date()).format('HH:mm:ss'))
setTimeout(() => {
console.log('交警处理事故4', moment(new Date()).format('HH:mm:ss'))
setTimeout(() => {
console.log('交警处理事故5', moment(new Date()).format('HH:mm:ss'))
setTimeout(() => {
console.log('交警处理事故6', moment(new Date()).format('HH:mm:ss'))
}, 1000)
}, 1000)
}, 1000)
}, 1000)
}, 1000)
}, 1000)
console.log('社会车辆3')
console.log('社会车辆4')
这种嵌套写法的致命问题的是:可读性极差、维护困难。简单的6个任务就已经嵌套6层,一旦某个任务需要修改逻辑、排查bug,就需要层层查找,非常繁琐------这就是"回调地狱",也是 JavaScript 异步编程中最头疼的问题。而 Promise 的出现,就是为了彻底规避这个问题,让异步代码变得简洁、优雅、易维护。
5. Promise 登场:解决回调地狱的优雅方案
Promise 是 JavaScript 内置的异步编程对象,专门用于处理异步任务的顺序执行,告别回调嵌套。它的基础语法非常简单,核心就是通过"链式调用"替代"嵌套调用",让异步代码的执行顺序清晰可见。
Promise 的基础语法如下:
js
new Promise()
↑这就是 Promise 的基本结构,它本质是一个构造函数,接收一个函数作为参数,这个函数内部又接收两个内置参数:resolve 和 reject,由 Promise 对象自动传入:
js
new Promise((resolve, reject) => {})
我们用通俗的方式解释这两个参数,结合前端常见场景(登录验证)理解更易上手:
- resolve:直译"解决、决定",在异步任务执行成功时调用,可传递成功的结果(比如登录成功的用户信息)。
- reject:直译"拒绝",在异步任务执行失败时调用,可传递失败的原因(比如登录时用户名/密码错误)。
简单总结:resolve = 异步成功,reject = 异步失败,两者都是用于标记异步任务的执行状态,并传递结果。
6. Promise 核心:状态与 resolve/reject 用法
Promise 有一个核心特性------状态管理,它的状态决定了异步任务的执行结果,且状态一旦改变,就无法再修改。Promise 有三种状态:
- pending:默认状态,异步任务未执行完成(等待中),此时 resolve 和 reject 都未被调用。
- fulfilled:成功状态,异步任务执行成功,调用 resolve 后,状态从 pending 变为 fulfilled。
- rejected:失败状态,异步任务执行失败,调用 reject 后,状态从 pending 变为 rejected。
我们通过代码验证这三种状态,先打印一个未调用 resolve/reject 的 Promise 对象:
js
const promiseObj = new Promise((resolve, reject) => {})
console.log(promiseObj)
执行结果截图:

从截图可以看到,Promise 的状态(PromiseState)是 pending,说明异步任务处于等待中,未执行成功或失败。
接下来,调用 resolve,查看状态变化:
js
const promiseObj = new Promise((resolve, reject) => {
resolve()
})
console.log(promiseObj)
执行结果截图:

状态从 pending 变为 fulfilled,说明异步任务执行成功,符合我们对 resolve 的定义。
再调用 reject,查看状态变化:
js
const promiseObj = new Promise((resolve, reject) => {
reject()
})
console.log(promiseObj)
执行结果截图:

状态从 pending 变为 rejected,说明异步任务执行失败,符合我们对 reject 的定义。
7. Promise 三大API:then、catch、finally 实战
Promise 对象内置了三个核心方法:then、catch、finally,用于接收异步任务的执行结果(成功/失败),也是实现链式调用的关键。我们结合登录场景,通俗讲解这三个方法的用法,再通过代码验证。
核心用法总结(登录场景类比):
- then() 方法:异步任务执行成功(调用 resolve)时触发,接收 resolve 传递的成功数据。比如登录成功,就进入 then 方法,执行后续操作(如跳转首页)。
- catch() 方法:异步任务执行失败(调用 reject)时触发,接收 reject 传递的失败原因。比如登录失败(用户名/密码错误),就进入 catch 方法,提示错误信息。
- finally() 方法:无论异步任务成功还是失败,都会触发,不依赖 resolve 和 reject。比如登录操作结束后,无论成功与否,都关闭加载弹窗。
验证1:调用 resolve,触发 then 和 finally
js
const promiseObj = new Promise((resolve, reject) => {
resolve('身份认证通过!')
})
promiseObj
.then((data) => { //data就是resolve传过来的内容 名称没有规定自定义即可
console.log(data)
})
.catch((error) => {
console.log(error)
})
.finally(() => {
console.log('我是finally')
})
执行结果截图:

结论:调用 resolve 后,代码先执行 then 方法(打印成功信息),再执行 finally 方法,catch 方法未触发,符合预期。
验证2:调用 reject,触发 catch 和 finally
js
const promiseObj = new Promise((resolve, reject) => {
reject('身份认证失败!')
})
promiseObj
.then((data) => {
console.log(data)
})
.catch((error) => { //error就是reject传过来的内容 名称没有规定自定义即可
console.log(error)
})
.finally(() => {
console.log('我是finally')
})
执行结果截图:

结论:调用 reject 后,代码先执行 catch 方法(打印失败原因),再执行 finally 方法,then 方法未触发,符合预期。
8. Promise 链式调用:彻底告别回调地狱
掌握了 Promise 的状态和三大API后,我们就可以用它的链式调用,解决前面提到的回调地狱问题。这里需要注意一个关键知识点:Promise 本身是同步的,但 then、catch、finally 方法中的回调函数是异步的,因此我们无需再用定时器,就能实现异步任务的顺序执行。
我们用 Promise 重写"交警处理多个事故"的案例,对比回调嵌套,看看链式调用的优雅之处:
js
console.log('社会车辆1')
console.log('社会车辆2')
const promiseObj = new Promise((resolve, reject) => {
resolve('交警处理事故1')
})
promiseObj
.then((data) => {
console.log(data)
return new Promise((resolve, reject) => {
resolve('交警处理事故2')
})
})
.then((data) => {
console.log(data)
return new Promise((resolve, reject) => {
resolve('交警处理事故3')
})
})
.then((data) => {
console.log(data)
return new Promise((resolve, reject) => {
resolve('交警处理事故4')
})
})
.then((data) => {
console.log(data)
})
.catch()
console.log('社会车辆3')
console.log('社会车辆4')
执行结果截图:

有同学会说:这也有嵌套啊?没错,但这种嵌套只有一层,无论有多少个异步任务,都只会嵌套一层,整体结构是"平铺式"的,而非"嵌套式"的,可读性大大提升。我们把链式调用简化来看,更直观:
js
promiseObj.then().then().then().then().then().then().catch()
这种平铺的链式结构,无论多少个异步任务,都能清晰看到执行顺序,彻底告别回调地狱的嵌套乱象。
补充一个进阶用法:给每个 then 方法单独设置错误捕获(then 方法可接收两个参数,第二个参数用于捕获当前 then 的错误):
截图参考(对应原文示例):

从截图可以看到,then 方法的第一个参数是"成功回调",第二个参数是"当前 then 的错误回调",结构如下:
js
promiseObj
.then((data) => {}, (error) => {})
.then((data) => {}, (error) => {})
.then((data) => {}, (error) => {})
.then((data) => {}, (error) => {})
.then((data) => {}, (error) => {})
我们用代码验证这种用法,在"交警处理事故3"时触发 reject,看看错误捕获的效果:
js
const promiseObj = new Promise((resolve, reject) => {
resolve('交警处理事故1')
})
console.log('社会车辆1')
console.log('社会车辆2')
promiseObj
.then(
(data) => {
console.log(data)
return new Promise((resolve, reject) => {
resolve('交警处理事故2')
})
},
(error) => {
console.log(error)
}
)
.then(
(data) => {
console.log(data)
let errorBtn = false
return new Promise((resolve, reject) => {
if (errorBtn) {
resolve('交警处理事故3')
} else {
reject('交通事故处理时候遇到特殊状况')
}
})
},
(error) => {
console.log(error)
}
)
.then(
(data) => {
console.log(data)
return new Promise((resolve, reject) => {
resolve('交警处理事故4')
})
},
(error) => {
console.log(error)
}
)
.then()
console.log('社会车辆3')
console.log('社会车辆4')
执行结果截图:

结论:当第二个 then 中触发 reject 后,代码会执行当前 then 的错误回调(打印错误信息),且后续的 then 方法不再执行,实现了精准的错误捕获,进一步提升代码的可维护性。
9. 总结与拓展
本文围绕 JavaScript 异步编程核心,从同步/异步代码的本质出发,剖析回调地狱的痛点,详细讲解了 Promise 的核心概念、状态管理、三大API(then/catch/finally)及链式调用用法,通过通俗的类比和实战代码,帮助大家快速掌握 Promise 的使用,彻底告别回调地狱。
核心知识点总结:
- Promise 是 JavaScript 异步编程的核心工具,用于解决异步任务顺序执行、回调嵌套的问题。
- Promise 有三种状态:pending(等待)、fulfilled(成功)、rejected(失败),状态一旦改变不可修改。
- resolve 标记成功、传递结果,reject 标记失败、传递原因;then 接收成功结果,catch 接收失败原因,finally 无论成败都执行。
- 链式调用是 Promise 的核心优势,通过 then 方法返回新的 Promise,实现异步任务的顺序执行,替代回调嵌套。
拓展建议:Promise 是前端异步编程的基础,后续的 async/await 语法(更简洁的异步写法)也是基于 Promise 实现的,掌握本文内容后,可进一步学习 async/await,提升异步编程效率。
学习本就是一场持久战,不需要急着一口吃成胖子。哪怕今天你只记住了一点点,这都是实打实的进步。
后续我还会继续用这种大白话、讲实战方式,带大家扫盲更多前端基础。
关注我,不迷路,咱们把那些曾经模糊的知识点,一个个彻底搞清楚。
如果你觉得这篇内容对你有帮助,不妨点赞+收藏,下次写代码卡壳时,拿出来翻一翻,比搜引擎更靠谱。
我是 Eugene,你的电子学友,我们下一篇干货见~