引言
在现代的Web开发中,JavaScript异步编程一直是一个备受关注的话题。长期以来,开发者们一直在寻求一种简洁而强大的解决方案,以有效地处理异步操作,避免回调地狱(callback hell)并提高代码的可读性和可维护性。正是在这样的背景下,Promise作为一种解决JavaScript异步编程难题的新思路应运而生。这篇文章,我将教会大家如何使用promise。
异步
异步(asynchronous)是指在程序执行过程中,不需要等待某个操作的完成,而可以继续执行后续的操作。相比于同步(synchronous)操作,异步操作可以提高程序的效率和响应性。
在编程中,异步操作通常用于处理那些耗时较长的任务,如网络请求、文件读写、数据库查询等。在传统的同步编程模型中,程序会在执行这些任务时被阻塞,直到任务完成后才能继续执行后续的代码。而在异步编程模型中,程序可以发起这些任务,然后立即继续执行后续的代码,而不需要等待任务完成。
我们来模拟一下异步:
js
function a() {
setTimeout (() => {
console.log('阿珍')
},1000);
}
function b() {
setTimeout(()=>{
console.log('阿强')
},0)
}
a()
b()
在a()
中我们让阿珍等待1s输出,此时我们的V8先执行b()
,等到1s结束后再执行了a()
,所以运行结果为=> 阿强 阿珍,这就是异步,先执行a()
发现方法需要等待就立刻执行接下来的程序,大大提高了js的执行效率。
异步存在的问题
当我们的b()
方法需要依赖a()
的结果进行运行,那么掘友们聪明的脑袋瓜子应该能发现,当a()
方法还在等待,而需要依赖a()
方法结果的b()
却提前运行了,这是不符合逻辑的,接下来我们来模拟一下异步的问题
js
function a(){
setTimeout(()=>{
consol.log('我去相亲了')
},2000)
}
function b(){
setTimeout(()=>{
consol.log('我要结婚了')
},1000)
}
a()
b()
显而易见,咱们得先相亲再结婚,并且相亲花费的时间要比结婚长,但是当我们运行程序时,输出的却是先结婚再相亲,这就是异步问题所在
回调地狱
以前的程序是如何解决这个问题的呢?
js
function a() {
setTimeout ((cb) => {
consol.log('我去相亲了')
cb()
},2000);
}
function b() {
setTimeout(()=>{
consol.log('我要结婚了')
},1000)
}
a(b)
以前,程序员们将b()
方法放入a()
中执行,于是便实现了a()
先执行b()
再执行。但是,我们的一个项目拥有非常多的方法,很多方法都要依赖别的方法结果来执行,所以程序员们得不停地在方法里回调其他方法,就像将所有的方法串成一根绳子,只要有一个地方断了,所有的程序都会出现错误,这就是回调地狱,使得代码的维护极其困难。
Promise
关于Promise的使用,我们先来看以下代码:
js
function xq(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('我相亲了');
resolve('相亲成功')//可以抛出一个值在.then()接收
},2000)
})
}
function marry(){
return new Promise((resolve, reject) => {
setTimeout(()=>{
console.log('我结婚了');
resolve()
},1000)
})
}
function baby(){
setTimeout(()=>{
console.log('小龙女出生');
},500)
}
xq().then((res)=>{
console.log(res);
return marry()//如果不使用return marry(),那么后面的.then()会调用默认的promise 从而baby()
//与marry()一起执行,形成异步
})
.then((res2)=>{
baby()
})
xq
函数返回一个Promise对象,表示"我相亲了"这个任务。在Promise中,我们使用setTimeout模拟了一个耗时2秒的相亲过程。当相亲成功后,Promise会调用resolve方法,并传递相亲成功的消息。marry
函数也返回一个Promise对象,表示"我结婚了"这个任务。在Promise中,我们同样使用setTimeout模拟了一个耗时1秒的结婚过程。当结婚成功后,Promise会调用resolve方法。baby
函数是一个简单的定时任务,用于模拟"小龙女出生"的过程。- 在主程序中,我们首先调用
xq()
函数,然后使用.then()
方法来处理相亲成功后的结果。在第一个.then()
中,我们输出了相亲成功的消息,并调用了marry()
函数。由于我们在.then()
中使用了return marry()
,所以后续的.then()
会等待marry()
的完成。 - 在第二个
.then()
中,我们输出了结婚成功的消息,并调用了baby()
函数。由于baby()
函数没有被包裹在Promise中,它会立即执行,而不会等待前面的异步操作完成。
细节
1.resolve 和 reject
在Promise中,resolve和reject是两个函数,用于表示Promise对象的状态。当一个异步操作成功完成时,我们会调用resolve函数来将Promise状态从pending(进行中)变为fulfilled(已成功);当异步操作发生错误或失败时,我们会调用reject函数来将Promise状态从pending变为rejected(已失败)。注意,如果在方法中调用了reject()方法,在全局调用方法时不捕获异常将会报错。
2.return marry()
细心的掘友们会发现,为什么我们在.then()
中要return marry()
呢?原来是因为如果不使用return marry()
,那么后面的.then()
会调用默认的promise 从而baby()
方法会和marry()
方法形成异步。
Promise.all() 和 Promise.race()
关于这两个方法我们先看以下代码:
js
function a(){
return new Promise((resolve,reject) =>{
setTimeout(()=>{
console.log('a');
resolve('yes')//顺利执行 reject() 执行出错
},2000)
})
}
function b(){
return new Promise((resolve,reject) =>{
setTimeout(()=>{
console.log('b');
resolve()
},1500)
})
}
function c(){
setTimeout(()=>{
console.log('c');
})
}
// a()
// b()
// c()
//Promise.all([a(),b()]).then(c)//c 将c函数体传进去 加括号是将c()运行结果传进去
//a(),b() 要有返回promise的能力
//all必须要接收到的所b有的promise状态都为resolve,then才会调用
Promise.race([a(),b()])
.then(c)
.catch((err)=>{
console.log(err);
})
//a,b谁快.then就接在谁后面执行
- Promise.all
c 将c函数体传进去 加括号是将c()
运行结果传进去,a()
,b()
要有返回promise的能力,all必须要接收到的所有的promise状态都为resolve
,then才会调用
- Promise.race
Promise.race
会等待a和b中任意一个任务完成,然后立即执行后续的操作。.catch()
方法是用来捕获方法中的异常,使其不报错并且更好地找到错误的地方
结尾
希望以上解析能够帮助您理解Promise的基本概念和使用方法。通过合理运用resolve和reject函数、Promise链式调用以及Promise.race等方法,我们可以更好地处理异步操作,实现更灵活、高效的代码设计