前言
异步,是任何编程都无法回避的话题。在promise出现之前,js中也有处理异步的方案,不过还没有专门的api能去处理链式的异步操作。所以,当大量的异步任务逐个执行,就变成了传说中的回调地狱。
function asyncFn(fn1, fn2, fn3) {
setTimeout(() => {
//处理第一个异步任务
fn1()
setTimeout(() => {
//处理第二个异步任务
fn2()
setTimeout(() => {
//处理第三个异步任务
fn3()
},2000)
},2000)
}, 2000)
}
function fn1(){
console.log('执行1')
}
function fn2(){
console.log('执行2')
}
function fn3(){
console.log('执行3')
}
asyncFn(fn1, fn2, fn3)
如果按照这么执行的话,等fn1执行完毕之后,再执行fn2,fn2执行完毕之后再执行fn3,这里只是简单的一个console.log,如果是真实的业务逻辑,那这看着可就太痛苦了。
promise就是用来解决这种问题的。
Promise基础
三种状态
pending代表状态未确定
fulfilled代表成功
rejected代表失败
类似于薛定谔的猫,开箱之前处于一种未知状态,只要你开了,那要么生,要么死,不会再发生更改。
记住,promise的状态是单向流的,一旦变化,永远不会发生更改。
基本用法
promise是个什么,答曰:构造函数。
成功的逻辑
//成功的promise
let prmFn1 = new Promise((resolve,jeject)=>{
setTimeout(()=>{
//模拟执行一个很消耗时间的任务
resolve('我是成功的值')
},5000)
})
prmFn1.then(resolve=>{
console.log(resolve) //5秒后输出:我是成功的值
})
失败的逻辑
//失败的promise
let prmFn2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('我是失败的原因')
},5000)
})
console.log(prmFn2,'失败任务还没有开始执行',prmFn2)
prmFn2.then(resolve=>{
//成功才回被调用
},reject=>{
console.log('失败了被调用',reject)
console.log('失败任务处理完毕',prmFn2)
})
这里发生了什么:
1.通过new Promise构造函数创建了一个promise函数,参数是一个函数。
2.这个函数有两个参数,一个是resolve函数,一个是reject函数,resolve代表异步任务成功需要调用的,reject函数代表异步任务失败了需要调用的。
3.promise创建了之后,会变成一个有状态的对象,这个对象内有一个属性then,then是一个函数,then函数中有两个参数,这两个参数都是函数,参数1一个是专门用来处理成功的,参数2专门用来处理失败情况的。
4.参数1函数的参数,就是在promise中resolve(value)中的value,参数2函数的参数,就是promise中reject(errorReason)中的errorReason。
感觉很绕是不是,这里就是函数式编程的经典范例,也是promise对于很多初级前端来说,很懵的地方。
解决方案:多学多练多理解
promise对象的方法
promise是一个构造函数,但是同时也是一个对象,这个对象中存在很多用于处理不同业务的方法,所以可以称promise为函数对象
resolve
定义成功后回调
//resolve
Promise.resolve('成功值').then(res => {
console.log(res, 'resolve的使用')
})
//等同于
new Promise((resolve, reject) => {
resolve('成功值')
}).then(res => {
console.log(res, 'resolve的使用')
})
reject
定义失败后回调
//reject
Promise.reject('失败值').then(res => {
console.log(res, '不会走成功')
}, rej => {
console.log(rej, '会走失败')
})
//等同于
new Promise((resolve, reject) => {
reject('失败值')
}).then(res => {
console.log(res, '不会走成功')
}, rej => {
console.log(rej, '会走失败')
})
then
根据状态值进行异步处理
当一个promise执行到then的时候,说明这个promise对象的状态值已经确定了,也就是只要执行到then方法里面,就说明前面的异步执行完成了,你可以根据返回的状态值进行异步的操作了。
then方法是promise的核心方法。
then是一个函数,接受两个参数,这两个参数都是函数,参数一处理成功回调,参数2处理失败回调
上面的代码都已经演示了,这里就不写了
catch
promise内发生意外报错回调处理
let prm = new Promise((resolve, reject) => {
let a = null
console.log(a.a)
resolve('成功')
})
prm.then(res => {
console.log(res, '进入then方法')
}).catch(error => {
console.log(error, '进入error')
})
all
所有异步都执行完毕后得到结果
异步都成功
let prm1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行prm1成功')
}, 3000)
})
let prm2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行prm2成功')
}, 1000)
})
let prm3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行prm3成功')
}, 5000)
})
Promise.all([prm1, prm2, prm3]).then(res => {
console.log(res, '三个都执行完毕')
})
异步中有失败
let prm1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行prm1成功')
}, 3000)
})
let prm2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行prm2成功')
}, 1000)
})
let prm3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('执行prm3失败')
}, 5000)
})
Promise.all([prm1, prm2, prm3]).then(res => {
console.log(res, '三个都执行完毕--成功')
}, rej => {
console.log(rej, '三个都执行完毕--有不成功')
})
allSettled
all方法能获取所有任务都成功之后的值,但是如果多个任务有成功有失败,就无法全部获取所有状态,但是allSettled可以做到
//allSettled方法
let pr1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('pr1成功')
},2000)
})
let pr2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('pr2失败')
},3000)
})
let pr3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('pr3成功')
},3000)
})
Promise.allSettled([pr1,pr2,pr3]).then(res=>{
console.log(res,'获取所有状态')
})
race
获取多个异步中最先执行完毕的结果
成功的情况
//race方法
let prm1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行prm1成功')
}, 3000)
})
let prm2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行prm2成功')
}, 1000)
})
let prm3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('执行prm3失败')
}, 5000)
})
Promise.race([prm1, prm2, prm3]).then(res => {
console.log(res, '成功逻辑')
},
rej => {
console.log(rej, '失败逻辑')
})
失败的情况
let prm1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行prm1成功')
}, 3000)
})
let prm2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('执行prm2失败')
}, 1000)
})
let prm3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('执行prm3失败')
}, 5000)
})
Promise.race([prm1, prm2, prm3]).then(res => {
console.log(res, '成功逻辑')
},
rej => {
console.log(rej, '失败逻辑')
})
any
有一个成功,就是成功。全都失败,就是失败
//any方法
let pr1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('pr1成功')
}, 2000)
})
let pr2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('pr2失败')
}, 3000)
})
let pr3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('pr3失败')
}, 3000)
})
Promise.any([pr1,pr2,pr3]).then(res=>{
console.log(res,'成功')
})
Promise.any([pr2,pr3]).then(res=>{
console.log(res,'失败')
})
finally
不管一个promise任务执行成功还是失败,执行完毕后都会进入这个方法
//finally方法
//成功
let prm4 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('成功的值')
},2000)
})
prm4.then(res=>{
console.log(res,'成功')
}).finally(val=>{
console.log(val,'finally方法')
})
//失败
let prm5 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('失败原因')
},2000)
})
prm5.catch(rej=>{
console.log(rej,'失败')
}).finally(val=>{
console.log(val,'finally方法')
})
项目中实际应用和注意事项
在当今的前端项目中,基本不会说用不到promise的,最常见的还是前后端数据交互,例如axios,fetch等请求都是通过promise封装后的,返回的值都是promise。
在项目实际场景中,会出现promise的回调链,下一个结果依赖上一个结果的返回值,形成异步任务的依赖关系。开发者需要去处理一个比较重要的问题,叫做中断promise链
中断promise链
//层层依赖形成执行链
Promise.resolve('初始化成功值').then(res => {
return new Promise(resolve => {
setTimeout(() => {
console.log(res)
resolve('第一层返回成功值')
}, 2000)
})
}).then(res => {
return new Promise(resolve => {
setTimeout(() => {
console.log(res)
resolve('第二层返回成功值')
}, 2000)
})
}).then(res => {
return new Promise(resolve => {
setTimeout(() => {
console.log(res)
resolve('第三层返回成功值')
}, 2000)
})
})
效果如下
我们可以根据每个任务的结果值去判定业务逻辑,是否需要中断后续的执行链条
代码改造如下
//中断promise链
Promise.resolve('初始化成功值').then(res => {
return new Promise(resolve => {
setTimeout(() => {
console.log(res)
// resolve('第一层返回成功值')
resolve('中断标识')
}, 2000)
})
}).then(res => {
if(res ==='第一层返回值'){
return new Promise(resolve => {
setTimeout(() => {
console.log(res)
resolve('第二层返回成功值')
}, 2000)
})
}else{
console.log('进入中断链方法')
//返回一个空的promise,就完成了中断
return new Promise(()=>{})
}
}).then(res => {
return new Promise(resolve => {
setTimeout(() => {
console.log(res)
resolve('第三层返回成功值')
}, 2000)
})
})
总结
三种状态:pending,fulfilled,rejected。分别代表待定,成功和失败。一旦修改,无法改变
九种方法:
resolve代表返回成功状态
reject代表返回失败状态
then是执行完毕进入的回调
catch是执行出错进入的回调
all代表全部成功,返回的是所有异步任务的成功值
allSettled代表全部执行完毕,即使不成功,也会返回全部异步任务的值
race代表获取先执行完毕的异步值
any代表有一个成功,就是成功。全都失败,就是失败
finnally代表你不管成功还是失败,都得来我这里。
实际应用:
多了去了,很多第三方包都会用到,前后端交互必备
中断执行链:
返回一个空的promise