在上一篇文章中我们了解到了JavaScript是单线程的,为了高效处理异步操作,JavaScript引入了一个核心概念——Event Loop(事件循环),随着ES6标准的推出,Promise成JavaScript中处理异步操作的一个重要工具。Promise不仅提供了更加优雅的链式调用语法,使得异步代码读起来更像同步代码,还通过.then、.catch等方法让我们能够更好地控制异步操作的执行流程和错误处理。今天让我们来了解promise和.then方法。
异步编程的需求
想象一下,你的应用需要从服务器获取数据,然后基于这些数据进行一些计算,最后更新UI显示结果。这是一个典型的异步场景,因为获取数据的操作可能需要时间,你不能简单地使用同步代码等待数据返回,否则整个应用界面会在此期间冻结。
回调函数与“回调地狱”
早期,JavaScript开发者通过回调函数来处理异步操作。当一个操作完成时,会调用预先定义好的函数。然而,当多个异步操作需要顺序执行,或者存在依赖关系时,回调函数的嵌套会迅速变得难以管理,形成所谓的“回调地狱”。这种代码不仅可读性差,而且调试困难,维护成本极高。我们先来看一个例子:
function a (cbB,cbC,cbD){
cbB(cbC,cbD);
}
function b (cb,cbD){
cb(cbD)
}
function c (cb){
cb()
}
function d (){
}
a(b,c,d)
你认为这个代码优雅吗?如果以后工作中让我碰到这样的代码,还要我去对它进行修改,我真的会亲切得问候写出这段代码的大哥。
Promise的三个状态
Promise是JavaScript中用于处理异步操作的一种编程模式,它以对象的形式对一个异步操作的结果(或其最终状态)进行封装,使得我们能够以同步的思维去编写异步代码。Promise有三种核心状态,它们定义了Promise对象在其生命周期中的不同阶段,这三种状态分别是:
-
Pending(等待中) :
- 初始状态,也是Promise对象创建后的默认状态。当一个Promise刚被创建但其异步操作还未完成时,它就处于Pending状态。此时,既没有被兑现(Resolved),也没有被拒绝(Rejected)。
- 在这个状态下,Promise既不是成功的,也不是失败的,它正处于“不确定”阶段,等待着异步操作的结果。
-
Resolved(已成功) :
- 当Promise代表的异步操作顺利完成,即达到预期结果时,Promise进入Resolved状态。
- 进入此状态后,Promise的
.then方法中注册的成功回调函数会被调用,用来处理成功的逻辑或数据。这个状态意味着操作已经成功结束,可以安全地使用操作结果了。 - 一旦Promise变为Resolved,它的状态就固定了,不会再变为Pending或Rejected。
-
Rejected(已失败) :
- 如果Promise代表的异步操作未能完成,出现了错误或异常,那么Promise就会进入Rejected状态。
- 处于Rejected状态时,Promise的
.catch方法或.then方法中用于处理失败情况的回调函数会被调用,以适当的方式处理错误。 - 同样,一旦Promise变为Rejected,其状态便不可改变,后续操作只能通过错误处理机制来应对。
这三种状态描述了Promise从创建到最终解决(无论是成功还是失败)的完整生命周期。值得注意的是,Promise的状态转换是单向的:Pending可以变为Resolved或Rejected,但Resolved和Rejected状态一旦确定,就不会再回到Pending或相互转换。这种设计确保了异步操作的结果能够被清晰、明确地处理,也便于开发者理解和维护代码。
如何转换promise的状态
1. 在Promise构造函数中转换状态
当你创建一个新的Promise时,通常会在其构造函数中传入一个执行器函数(executor function),该函数接受两个参数:resolve和reject。正是通过调用这两个函数,你可以控制Promise的状态变化。
- 使用
resolve(value)转换为Resolved状态: 调用resolve函数意味着异步操作成功完成,Promise状态变为Resolved。你可以向resolve传递一个值(任何合法的JavaScript值),这个值将会作为成功的结果传递给后续.then方法的回调函数。
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('操作成功');
}, 1000);
});
- 使用
reject(reason)转换为Rejected状态: 如果异步操作遇到错误或未能按预期完成,应调用reject函数,将Promise状态变为Rejected,并可传递一个原因(error对象或任何描述错误的信息)给后续.catch方法的回调函数。
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('操作失败');
}, 1000);
});
2. 在链式调用中转换状态
在Promise链中,每个.then或.catch方法都有机会返回一个新的Promise。如果这个新Promise内部再次调用了resolve或reject,它将影响整个链的状态:
- 从
.then中返回的新Promise: 如果.then回调中返回了一个新的Promise,并且该Promise最终resolve,则整个链将继续以Resolved状态前进,并将新Promise的resolve值传递给下一个.then。 - 从
.then中抛出错误或直接调用reject: 如果.then回调中抛出了一个错误或显式调用了reject,这会导致链中的下一个.catch被调用,状态转换为Rejected。 .catch中的状态转换: 类似地,.catch处理错误后,可以通过返回一个新的Promise并调用resolve来恢复到Resolved状态,继续后续的.then链。
不过我们需要注意一点:then方法支持链式调用,因此then默认也会返回一个promise对象,但是状态默认是pending,这就会导致后面的then用不上前面的then 的状态,从而继续往前查找,举个例子:
xq()
.then( ()=>{
return marry() // 这里不用return status状态会继承xq 的status状态 所以不用return baby会比marry先执行
})
.then( ()=>{
baby()
})
如果不使用return marry(),而是直接调用marry()而不返回其Promise,那么baby()可能会在marry()操作完成之前就开始执行。这是因为没有return语句表明当前.then的执行并不依赖于marry()的结果,导致Promise链不会自动等待marry()完成。在这种情况下,baby()的执行将不会受到marry()完成状态的影响,可能导致不符合预期的并发或乱序执行。
3. 使用Promise静态方法转换状态
除了在Promise构造函数内部,还可以通过Promise的静态方法(如Promise.resolve(value)和Promise.reject(reason))直接创建已完成(Resolved)或已失败(Rejected)的Promise对象,从而间接控制状态转换。
.then方法的魅力
Promise的核心在于它的.then方法。.then方法注册了两个回调函数:第一个在Promise变为resolve时被调用,处理成功的逻辑;第二个(可选)在Promise变为rejected时被调用,处理错误。通过链式调用.then,我们可以以一种更线性、易于理解的方式组织异步操作,而非嵌套回调。
fetchData()
.then(data => processData(data))
.then(result => updateUI(result))
.catch(error => handleError(error));
上述代码展示了如何使用.then链来依次执行异步操作:首先获取数据,然后处理数据,最后更新UI。如果在任何一步中发生错误,.catch会捕获并处理该错误,保持代码的整洁和可维护性。
Promise的高级特性
- 返回新的Promise:在
.then或.catch中返回一个新的Promise,可以进一步控制异步流程,实现更复杂的逻辑。 - Promise.resolve/reject:可以直接创建已经解决或拒绝的Promise,用于快速封装操作结果或错误。
- Promise.all/race:处理多个Promise集合,
.all等待所有Promise都完成(或任何一个失败),.race则取第一个完成(无论成功还是失败)的结果。
结语
Promise及其.then方法极大地改善了JavaScript异步编程的体验,通过提供一种更加结构化的方式来处理异步操作,有效避免了回调地狱,提高了代码的可读性和可维护性。尽管后来async/await语法糖提供了更接近同步编程的体验,但理解Promise的底层机制仍然是每位前端开发者不可或缺的知识基础。掌握Promise,你就掌握了JavaScript异步编程的关键工具之一。
好的,这次的内容就分享到这了,如果小友觉得整的还不错的,可以留下一个小小的赞帮助俺找回自己的脑子,谢谢啦!!!