拥抱Promise,迈向异步编程的新篇章
引言
Promise
是 JavaScript 中用于处理异步操作的一种编程模式,它为异步代码提供了一种更优雅、更易于理解和维护的解决方案。相比于传统的回调函数,Promise
能够以链式调用的方式组织代码,避免了回调地狱,让js执行效率更高。
为什么要用promise?
function a() {
setTimeout(function () {
console.log('敲代码');
}, 1000)
}
a()
function b() {
console.log('打游戏')
}
b() // 先执行
在上面这段代码中,先调用的是函数a
,那为什么是函数b
先执行呢,难道不是一秒后先执行a
紧接着执行b
吗?
这是因为在JavaScript中,代码是按照顺序执行的,但是当遇到异步操作时,就不一样了,上面的代码中就涉及到了一个典型的异步操作:setTimeout
,为了执行效率更高,js引擎不会等待这个延时函数完成,而是继续执行后面的代码。
如果想要在一秒后先执行a
紧接着执行b
,我们就要这么写
function a(cb) {
setTimeout(() => {
console.log('敲代码')
cb()
}, 1000);
}
function b() {
console.log('打游戏')
}
a(b)
这样一来,b
函数的执行就会依赖a
的执行结果,这种调用函数的方式我们称之为回调,如果函数数量多起来的话,过多的回调函数会使代码难以阅读和维护,就会形成回调地狱,因此引入了一个新的用于处理异步操作的一种编程模式---Promise
什么是Promise?
Promise 是一个构造函数(也可以看成是一个对象),代表一个异步操作的结果,这个结果可能已经完成、正在进行中或尚未开始。它有三种状态:
- pending(等待中):初始状态,既没有被兑现,也没有被拒绝
- fulfilled(已成功):意味着操作成功完成
- rejected(已失败):意味着操作失败
一旦 Promise 的状态从 pending 变为 fulfilled 或 rejected,就不会再改变
promise的基本用法(all,race)
来看一段代码
function Blind date() {
return new Promise((resolve, reject) => {
// 回调函数的参数就是一个函数体
setTimeout(() => {
console.log('相亲')
resolve('成功') //通常这里放的是请求到的数据
}, 2000)
})
}
function marry() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('结婚')
resolve('顺利')
}, 1000)
})
}
function baby() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('孩子')
resolve()
}, 500)
})
}
function baby2() {
setTimeout(() => {
console.log('二胎')
}, 300)
}
Blind date()
.then((res) => {
console.log(res)
return marry() // 覆盖掉默认自带的promise
})
.then((res2) => {
console.log(res2)
return baby()
})
.then(() => {
baby2()
})
代码有点长,我们来梳理一下
首先我们创建了四个函数,分别是Blind date()
,marry()
,baby()
和baby2()
,如何才能让他们按顺序顺利执行呢,我们从第一个函数看起:
我们通常使用 new Promise()
来创建构造函数,Promise
里面接收一个参数,这个参数是一个回调函数,然后我们在函数体内调用了resolve
,因为写在Promise
函数里的参数,本身就是一个函数,所以是可以直接调用的,在new
了一个构造函数之后一定会得到一个实例对象,也就是说,在Blind date
执行完之后一定会得到一个对象,然后看到下面的Blind date()
后面接了.then
方法,它里面也接收一个实参为回调函数,可以在实参里写你想实现的效果,这里的形参res
的参数是用来接收前面resolve
出来的东西,这样就能在控制台打印出相亲成功
了。
接下来如何让他结婚呢,这个时候就可以直接在.then
里返回marry
函数,这里的return
是一定一定要写的,否则baby
就不会等marry
执行完再执行(baby
的执行时间更短),只要写了return
,第一个.then
里返回的实例对象Promise
不执行完是无法继续执行到第二个.then
的,原因在于实例对象能够访问.then
显然.then
是写在了Promise
构造函数的原型上,它身上有一个then
方法,才可以在我们new
了一个Promise
得到的实例对象后面可以接一个.then
。如果不写返回值,它会自动有一个默认值,会创建一个新的Promise
出来,显然不是我们想要的marry()
函数,只要写了返回值,就会覆盖掉默认自带的Promise
,这样一来就可以看懂这段代码了。
在这个例子中我们已经涉及到了一个新的概念---
then的链式调用
通过.then
方法,我们可以将一系列基于前一个Promise
结果的操作链接起来,将多个操作依次执行,在这个例子中,每个.then
都是在前一个Promise
成功(resolve)后执行的,而.catch
则用来捕获整个链中可能出现的错误,也可以说.catch
是为rejected
兜底的,在返回的是错误值的时候不会影响代码继续向下执行,来看下一个例子就可以清晰地明白这一点:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('a')
resolve('yes') // 代表执行顺利反之失败
}, 1000)
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('b')
reject('no')
}, 1500)
})
}
//resolve用then
a()
.then((res) => {
console.log(res)
})
//reject用catch
b()
.catch((err) => {
console.log(err)
})
接着上面的例子再来介绍两个常用的方法,在函数ab
后面新建一个函数c
function c() {
console.log('c')
}
如何让函数c
在函数ab
执行完之后执行呢?
有一个all方法可以实现这个效果:
Promise.all([a(), b()]).then(c)
但是!这样还不够严谨,all
必须接收到的所有Promise
状态都为resolve
,then
才会调用。
其实,实现这个效果以跑的最慢的函数为准执行回调的,那么如何以跑的快的函数为基准来执行回调呢?
这里也有一个方法race来实现:
// 在函数a内调用reject('yes')
Promise
.race([a(), b()])
.then(c)
.catch((err) => {
console.log(err) //a yes b
})
在race
方法中,并不需要所有的Promise
都必须是非rejected
状态,这意味着,即使所有其他Promise
都还在pending
状态,或者最终会变为fulfilled
状态,只要其中一个Promise
变为rejected
,Promise.race
就会立即结束,返回一个rejected
的Promise
,在这个例子中,由于a()
设置的延迟时间较短(1000ms),它比b()
(1500ms)先完成,并且执行了reject('yes')
。因此,Promise.race
立即结束,并跳转到.catch((err) => { console.log(err); })
这一部分,打印出yes
,虽然Promise.race
在a()
拒绝后就已经“决定”了结果,但这并不意味着它能直接阻止b()
的执行。b()
作为一个独立的异步操作,已经在事件队列中排队等待执行,因此它的setTimeout
依然会执行,打印出b
,总结来说,Promise.race
确实是在a()
或b()
中任意一个Promise
完成时就“结束比赛”,但这里的“结束”是指它立即根据已完成的Promise
的状态(fulfilled
或rejected
)来决议自己,并调用相应的.then
或.catch
处理程序,它并不能阻止其他Promise
继续执行它们自己的逻辑,特别是那些已经在事件循环队列中排队的异步操作。
结语
嘿,朋友!经过这一路的探索,Promise是不是感觉没那么神秘,甚至还有点儿亲切了呢?就像是个总能在关键时刻帮你排忧解难的好伙伴,Promise在JavaScript的异步世界里,就是我们的好帮手。想象一下,以前写异步代码就像安排一场复杂的多米诺骨牌,一不小心,整个序列就乱套了。但现在有了Promise,就像是给每个骨牌装上了智能导航,它们自己知道什么时候该倒下,而且还能互相通知:“嘿,我搞定了,该你上场啦!”这样,咱们的代码就变得井井有条,看起来舒服多了。
不管是通过.then
串起的一连串操作,还是用all
集结小伙伴们一起行动,或是race
里那个速度最快的先冲线,Promise总有办法让异步任务变得既高效又优雅。就连处理错误,也有.catch
在那里默默守候,随时准备接手烂摊子,不让任何小差错影响到大局。最重要的是,Promise让我们学会了怎样在纷繁复杂的异步操作中找到那份从容和淡定。现在,就算面对再复杂的异步逻辑,也能像泡一杯茶一样,慢慢享受过程,不再担心会被回调地狱困住。
好了,今天的旅程就到这里,下次见!
转载自:https://juejin.cn/post/7376158566598197263