拥抱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