前端进阶攻略之ES6中的Promise
一.什么是Promise对象?
Promise是异步编程的一种解决方案,比起传统的解决方案(回调函数和事件),它显得更加的强大和方便(具体请看下文)。从语法上来讲,Promise是一个对象,从它可以获取异步操作的消息。Promise对象提供统一的API,各种异步操作都可以用同样的方法进行处理。
二.Promise有什么用?
大家一致会回答,回调地狱。那什么又是回调地狱?它有什么缺点?
回调: 运行某个函数实现某个功能的时候,并传入一个函数作为参数,当发生某件事情时,会执行该函数
回调地狱:异步操作需要等待之前的异步操作完成,不管用回调或者事件,都是在不断的嵌套。
<!-- 点击btn1会触发btn2事件的注册,点击btn2则会触发btn3的注册 -->
<button id="btn1">btn1:给btn2注册事件</button>
<button id="btn2">btn2:给btn3注册事件</button>
<button id="btn3">btn3:点击后弹出HelloKitty</button>
<script>
const btn1 = document.getElementById("btn1")
const btn2 = document.getElementById("btn2")
const btn3 = document.getElementById("btn3")
btn1.onclick = function(){
btn2.onclick = ()=>{
btn3.onclick = ()=>{
alert("HelloKitty")
}
}
}
// 回调: 运行某个函数实现某个功能的时候,并传入一个函数作为参数,当发生某件事情时,会执行该函数
// 回调地狱:某个异步操作需要等待之前的异步操作完成以后,无论是回调还是函数,都会陷入不断的嵌套
</script>
如果有以上场景:点击btn1会触发btn2事件的注册,点击btn2则会触发btn3的注册,那么传统我们可能会在btn1里面嵌套btn2,btn2里面嵌套btn3,以上场景就会形成回调地狱,这在我们写代码中是非常可怕的。
Promise是回调地狱的解决方案之一,我们使用Promise的语法来解决回调地狱的问题,使代码拥有可读性和可维护性(ES6 规定,Promise对象是一个构造函数,用来生成Promise实例,针对所有的异步)。。
三.Promise的基础用法?
ES6将某一件事情可以发生异步操作的时候,分为2个阶段 unsettled 未决阶段 settled 已决 阶段
事情总是从未决阶段走向已决阶段 ,并且为未决阶段有控制已决阶段的能力,可以决定事情的最终走向状态
es6将程序分为3种状态 pending(等待) resolved(处理完成) rejected(处理失败)
pending :挂起(等待) 处于未决阶段 表示事情还在挂起中,最终结果没有出现
resolved : 已处理 处于已决阶段 表示已经出现结果,并且可以按照正常的逻辑进行下去的一种结果
rejected : 已拒绝 处于已决阶段 表示已经出现结果,无法安照正常的逻辑进行下去的一种结果
把事情从pending状态推向resolved状态的过程,传递的是有效数据
把事情从pending状态推向rejected状态的过程,传递的是错误信息
注意:无论是状态还是阶段都是不可逆的
resolved 这是一个正常的已决阶段下的状态,后续处理为thenable
rejected 这是一个非正常的已决阶段下的状态,后续处理为catchable
基本语法:
<!-- promise本质上是一个构造函数 -->
<script>
const pro = new Promise((resolve,reject)=>{
// 未决阶段
// 通过调用resolve函数将promise推向已决阶段中的resolved状态
// resolve和reject只能写一个,如果使用多个则只有第一个有效
// 传递参数只能有一个,可以是任意数据类型的,表示推向状态的数据
reject(123)
// resolve({})
})
// console.log(pro)
pro.then(data=>{
// thenable promise已经是已决阶段的resolved状态
console.log(data) //123
},err=>{
// catchable promise已经是已决阶段的rejected状态
console.log(err)
})
</script>
- resolve函数的作用是在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
- reject函数的作用是在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去
- Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
- reject后的东西,一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入catch方法。
- then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
thenable和catchable函数是异步的,就算会立即执行,也会加入等待队列中的微任务
const pro = new Promise((resolve,reject)=>{
console.log("a")
reject(123)
setTimeout(()=>{
console.log("b")
},0)
})
pro.then(data=>{
console.log(data)
})
pro.catch(err=>{
console.log(err)
})
console.log("c")
所以最后控制台输出如下:先执行同步》异步》微任务》宏任务
pro.then方法可以写thenable和catchable函数,pro.catch方法只能写catchable函数
const pro = new Promise((resolve,reject)=>{
throw new Error("123")
})
pro.then(data=>{
console.log(data)
})
pro.catch(err=>{
console.log(err)
})
在pro.then中既可以写thenable函数也可以写catchable函数,pro.catch方法只能写catchable函数
在未决状态的处理函数中,如果发生未捕获的错误,则会将状态推向rejected中,并且会被catchable所捕获
const pro = new Promise((resolve,reject)=>{
throw new Error("123")//未捕获
// reject("123")
try{
throw new Error("456") //已捕获
}catch(e){
console.log(e)
}
resolve(123)
// reject(456)
})
pro.then(data=>{
console.log(data)
})
pro.catch(err=>{
console.log(err) //123
})
一旦状态推向已决阶段,无法做任何改变,所以使用未捕获的throw new Error(“123”),相当于reject(“123”),所以最终输出结果为123
如果前面的promise的后续处理,返回的是一个promise对象,则返回的promise状态和后续处理返回的promise状态保持一致
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
const pro2 = new Promise((resolve,reject)=>{
reject(2)
})
console.log(pro2)
const pro3 = pro1.then(result=>{
return pro2 //推一个对象,这个对象不能是promise对象,推出去的是promise的结果状态
// reject(2)
})
pro3.then(data=>{
console.log(data)
},err=>{
console.log(err)
})
console.log(pro3)
面试题:
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
console.log(pro1) //1
const pro2 = pro1.then(result=>result*2) // 通过then方法调用后会返回一个新的promise对象
console.log(pro2) //2 //pending状态 等待前面的处理结果
因为pro1.resolve的值为1,pro2 返回的是一个pro1对象,如果返回的是一个promise对象,则返回的promise状态和后续处理返回的promise状态保持一致,所以最终输出结果为2,
下例的思路也是和上面是一样的,如果返回的是一个promise对象,则返回的promise状态和后续处理返回的promise状态保持一致,所以最终输出结果为3,
const pro1 = new Promise((resolve,reject)=>{
reject(1)
})
const pro2 = pro1.then(result=>{
console.log(result)
return result * 2
},err=>{
console.log(err) //1
return err
})
console.log(pro2)
pro2.then(result=>{
console.log(result*3) //3
},err=>{
console.log(err)
})
四.Promise的实例方法
4.1.then(data=>{},err=>{} ) 注册一个后续处理函数
const pro = new Promise((resolve,reject)=>{
resolve(123)
})
pro.then(data=>{
console.log(data);
},err=>{
console.log(err);
})
在Promise的实例上面可以调用then()方法,注册一个后续处理函数,pro.then方法可以写thenable和catchable函数,pro.catch方法只能写catchable函数
4.2catch(err=>{}) 注册一个catchable处理函数
4.3finally() ES2018出来的新增方法,没有参数,当promise为已决状态是则会运行该函数
const pro= new Promise((resolve,reject)=>{
resolve(1)
})
pro.finally(()=>{
console.log("finally")
})
pro.then(result=>{
console.log(result)
})
finally()方法的作用:测试promise和对于异步操作完成以后不论结果如何的后续处理
五.Promise构造函数成员(静态成员)
5.1resolve()
const pro1=new Promise((resolve,reject)=>{
resolve(123);
})
const pro = Promise.resolve(pro1)
5.2reject()
const pro = new Promise((resolve,reject)=>{
resolve(123)
})
const pro2 = Promise.resolve(pro)
console.log(pro2);
console.log(pro,pro2)
console.log(pro2 === pro) //true
特殊情况:resolve和reject两个静态成员如果传递的是promise,则直接返回一个promise对象
5.3all() 这个方法会返回一个新的promise对象,如果里面所有的promise对象都成功了才会触发,一旦有一个失败 则该promise失败
const proms = [];
for(let i = 0;i<10;i++){
proms.push(new Promise((resolve,reject)=>{
// resolve(1)
setTimeout(()=>{
if(Math.random() < 0.9){
console.log(i,"成功")
resolve(i)
}else{
console.log(i,"失败")
reject(i)
}
})
}))
}
const pro = Promise.all(proms)
pro.then(data=>{
console.log("全部成功",data)
},err=>{
console.log("有失败的",err)
})
5.4race(arr) 返回一个全新的promise对象,当数组中任意一个promise对象完成时,就马上回去使用完成好的promise对象的结果,不管这个 结果是成功还是失败。
function getRandom(min,max){//promise完成时间
return Math.floor(Math.random() * (max-min) + min )
}
const proms = []
for(let i =0;i<10;i++){
proms.push(new Promise((resolve,reject)=>{
setTimeout(()=>{
if(Math.random() < 0.5){
console.log(i,"成功")
resolve(i)
}else{
console.log(i,"失败")
reject(i)
}
},getRandom(1000,5000))
}))
}
const pro = Promise.race(proms)
pro.then(data=>{
console.log("有promise完成",data)
},err=>{
console.log("有promise失败的",err)
})
六.Promise的demo
用Promise写的一个相亲小demo,同时向20个女生表白,当表白成功时,会拒绝其他表白成功的女生。
function biaobai(nvsheng){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
if(Math.random() < 0.2){
console.log(nvsheng,"同意")
resolve(true)
}else{
console.log(nvsheng,"拒绝")
resolve(false)
}
},500)
})
}
let hasAgree = false;//是否有人同意
const proms = [];
for(let i =0 ;i<20;i++){
const a = biaobai(`女生${i}`).then(data=>{
if(data){
if(hasAgree){
console.log("发错信息了,刚刚是我弟发送了")
}else{
hasAgree = true;
console.log("小明很开心,有女朋友有了")
}
}
return data;
})
proms.push(a)
}
console.log(proms)
Promise.all(proms).then(result=>{
console.log('恋爱日志',result)
})
用promise封装AJAX:
function show(url,type,data){
return new Promise((resolve,reject)=>{
$.ajax({
type:type, //提交方式
url:url, //地址
data:data,
success:function(data){ //请求成功
console.log(data);
resolve(data);
},
error:function(err){ //请求失败
console.log(err);
reject(err);
}
})
})
}
show("你的url","get",).then(data=>{
console.log(data);
},err=>{
console.log(err);
})