一、什么是promise
我们通常都会去 new 一个Promise,什么东西可以new啊? 是不是构造函数可以new, 通过控制台dir一下Promise
通过上面的输出,你可以知道promise是一个构造函数
Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。
Promise对象有以下两个特点:
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Promise是Es6新增的构造器,用来提优化异步代码的写法,Promise中文意为承诺,承诺它一段时间后返回给你最终的结果。
首先了解三个状态,用setTimeout模拟实现一个ajax
三个状态
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled): 意味着操作成功完成。
- 已拒绝(rejected): 意味着操作失败。
举个栗子吧,小秃和小花去表白,小秃说 我喜欢你,你喜欢我吗? 这时候 小花肯定有两种回复 答应和拒绝
pedding:你可以理解为小秃正则等待小花的回复,但是目前没有回复的准确结果,例如上面代码的setTimeout 等待的2秒就是等待时间
fulfilled: 代表 我同意啦 我也暗恋你很久了!
rejected: 代表 你不配 渣男!
二、 Promise格式及PromiseState
// 箭头函数
let p1 = new Promise((resolve,reject)=>{})
// 普通声明
let p2 = new Promise(function (resolve,reject) {})
(1)pending
pending:待定,是一个等待最终结果的状态,也是一个初始状态,如果你在函数中不调用resolve,reject最终都是一个一直等待的状态,但是不调用,并没有什么意义,一般常用与调用resolve、reject给与相应的状态,总结:你只需要知道这是一个等待最终结果的状态,至于之后会发生状态,并不会知道
let p1 = new Promise((resolve,reject)=>{})
(2)fulfilled 或 resolved
fulfilled代表已兑现、成功 ,意思是承诺兑现了 从pending=>fulfilled 的过程,在之前的浏览器版本可能会出现resolved的情况都是代表成功,当调用resolve函数时 就会进入成功(fulfilled )的状态
let p1 = new Promise((resolve,reject)=>{
resolve(1)
})
(3)rejected
rejected: 代表拒绝,失败,意思是承诺没有实现从pending=》rejected的过程 当调用 失败(reject)的状态
let p1 = new Promise((resolve,reject)=>{
reject(1)
})
小结
最初创建对象的状态是pending 当调用了 resolve方法时 就会进入 fulfilled 状态 当调用reject方法 就会进入 rejected 状态
当状态一旦发送改变 就不会重新改变 例如已经从pending 到 fulfilled 了就不会重新改变到 rejected
let p1 = new Promise((resolve,reject)=>{
resolve()
reject()
})
console.log(p1); //上面代码只会从 pending状态到fulfilled状态,并不会去执行最后的rejected 状态
三、PromiseResult
let p1 = new Promise((resolve,reject)=>{
resolve(1)
})
console.log(p1);
通过resolve和rejected传入参数时通过打印就会发现PromiseResult的值(值可以是任意数据类型),怎么拿到PromiseResult的值呢???
new Promise((resolve,reject)=>{
resolve('123')
}).then(res=>console.log(res+'成功就会执行'))
.catch(err=>{console.log(err+'失败就会执行')})
.finally(()=>{console.log('不管成功还是失败都会执行');})
在then和catch方法中它的参数是一个回调函数,在这个回调函数中的参数中就可以拿到resolve传过来的值,也就是promiseValue
- 当调用resolve函数时传过去的参数就会到.then中的回调函数的参数中 状态pending===》fulfilled
- 当调用reject函数时传过去的参数就会到.catch 中的回调函数的参数中 状态pending===》rejected
** .then中也可以写两个参数 第二个参数是可选的**
当状态是pending===》fulfilled状态时会到.then中的第一个参数中的函数 pending===》rejected 回到.then中的第二个参数中的函数
new Promise((resolve,reject)=>{
reject(123)
}).then(
(res)=>{ console.log(res+'成功') },
(err)=>{ console.log(err+'失败') }
)
当你不去接收处理的 rejected状态时 会报出Uncaught (in promise) 中文意为未兑现(承诺)
new Promise((resolve,reject)=>{
reject(123)
}).then((res)=>{ console.log(res+'成功') })
四、promise链式调用
在了解链式调用之前 先知道在这个.then函数中返回的是仍一个新的promise对象
let p1 = new Promise((resolve,reject)=>{
resolve('123')
})
let p2 = p1.then(res=>{})
console.log(p2);
console.log(p1===p2); //false
p2中的PromiseResult 拿到的返回值是p1.then 函数中的返回值而p1.then函数中 没有返回值 那么返回值就是undefined
p2的promiseResult = p1.then的返回值
思考: 为什么p2拿到的状态是fulfilled呢?不是rejected呢?
1.当.then中返回的不是promise对象时(包括undefined),p2的状态 一直都是fulfilled
2.只有当.then中的 返回的是promsie对象 并且调用的状态是reject()就会失败 rejected
下面这个例子就会返回 rejected
let p2 = p1.then(()=>{
let p = new Promise((resolve,reject)=>{
reject()
})
return p
})
console.log(p2);
当 .then函数中出现错误时 p2拿到的PromiseState的值是rejected,PromiseValue拿到的值是.then中出现的错误
let p1 = new Promise((resolve, reject) => {
resolve()
});
let p2 = p1.then(()=> {
console.log(a); // 这里故意犯错
});
console.log(p2);
既然 p2 也是一个promise对象 我们就可以继续使用.then, 接下来 链式调用就开始了
let p1 = new Promise((resolve, rejected) => {
resolve(1)
})
let p2 = p1.then((res) => {
console.log(res); //1
return 2
})
p2.then((res)=>{
console.log(res); //2
})
当然也可以省略定义变量名 一直.then .then ~~的写
new Promise((resolve, rejected) => {
resolve(1)
}).then((res) => {
console.log(res);
return 2
}).then((res) => {
console.log(res);
})
五、回调地狱
模拟ajax 回调地狱 每次调用setTimeout 都是拿到上一次的值-1
function fn1(a) {
setTimeout(() => {
console.log(a);
a--;
setTimeout(() => {
console.log(a);
a--
setTimeout(() => {
console.log(a);
a--
setTimeout(() => {
console.log(a);
a--
setTimeout(() => {
console.log(a);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}
fn1(5)
看到上面的代码 像不像地狱级别的嵌套 如果每次ajax 都要拿上一次的值去发ajax 造成了很不好的维护性,读代码也很困难,如果解决呢,这就用到了promise
错误写法
promise为什么能解决回调地狱 例如下面这个例子并没有去解决回调地狱 仍然造成了回调地狱
function fn1(a) {
new Promise((res, rej) => {
res(a)
})
.then((params) => {
return new Promise((res, rej) => {
setTimeout(() => {
console.log(params);
res(--params)
return new Promise((res, rej) => {
setTimeout(() => {
console.log(params); res(--params)
return new Promise((res, rej) => {
setTimeout(() => {
console.log(params); res(--params)
return new Promise((res, rej) => {
setTimeout(() => {
console.log(params); res(--params)
return new Promise((res, rej) => {
setTimeout(() => {
console.log(params); res(--params)
}, 1000)
})
}, 1000)
})
}, 1000)
})
}, 1000)
})
}, 1000)
})
})
}
let a = 5
fn1(a)
注意 优化成promise并不是在一个 .then里面 继续做操作 包括你在.then里继续new一个promise 并且一直嵌套这.then.then 还是会产生回调地狱
正确写法
优化成 promise 发现就不会出现嵌套关系,而且不管写多少个 也是条条有序
function fn1(a) {
new Promise((res,rej)=>{
res(a)
})
.then((params)=>{
return new Promise((res,rej)=>{
setTimeout(() => { console.log(params);res(--params) }, 1000) })
})
.then((params)=>{
return new Promise((res,rej)=>{
setTimeout(() => { console.log(params);res(--params) }, 1000) })
})
.then((params)=>{
return new Promise((res,rej)=>{
setTimeout(() => { console.log(params);res(--params) }, 1000) })
})
.then((params)=>{
return new Promise((res,rej)=>{
setTimeout(() => { console.log(params);res(--params) }, 1000) })
})
.then((params)=>{
return new Promise((res,rej)=>{
setTimeout(() => { console.log(params);res(--params) }, 1000) })
})
}
let a=5
fn1(a)
实例中优化
在下面中的demo中 在一个.then中又嵌套了两次.then 这就造成了回调地狱 ,你不需要关系内容 只需要知道如何解决
return new Promise((resolve, reject) => {
if (ctx.rootState.user.profile.token) {
// 没有修改登录后的sku接口
const oldGoods = ctx.state.list.find(it => it.skuId === oldSkuId)
console.log(newSku)
// 删除 拿到之前的skuId删除
deleteCart([oldSkuId]).then(() => {
// 添加最新的sku进入购物车
return insertCart({ skuId: newSku.value.skuId, count: oldGoods.count }).then(() => {
// 请求最新的数据
return findCartList().then((data) => {
ctx.commit('setCartList', data.result)
resolve()
})
})
})
优化
return new Promise((resolve, reject) => {
if (ctx.rootState.user.profile.token) {
// 没有修改登录后的sku接口
const oldGoods = ctx.state.list.find(it => it.skuId === oldSkuId)
console.log(newSku)
// 删除 拿到之前的skuId删除
deleteCart([oldSkuId])
.then(() => { // 添加最新的sku进入购物车
insertCart({ skuId: newSku.value.skuId, count: oldGoods.count })
console.log(1)
})
.then(() => { findCartList(); })
.then((data) => {
ctx.commit('setCartList', data.result)
resolve()
})
再次优化 省略箭头函数中的大括号
return new Promise((resolve, reject) => {
if (ctx.rootState.user.profile.token) {
// 没有修改登录后的sku接口
const oldGoods = ctx.state.list.find(it => it.skuId === oldSkuId)
console.log(newSku)
// 删除 拿到之前的skuId删除
deleteCart([oldSkuId])
// 添加最新的sku进入购物车
.then(() => insertCart({ skuId: newSku.value.skuId, count: oldGoods.count }))
.then(() => { findCartList(); console.log(2) })
.then((data) => {
ctx.commit('setCartList', data.result)
resolve()
})
六、promise.all()
与then同级的另一个方法,all方法,该方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后并且执行结果都是成功的时候才执行回调。
将上述方法复制两份并重命名promiseClick3(), promiseClick2(), promiseClick1(),如下:
function promiseClick1(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
function promiseClick2(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
function promiseClick3(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
Promise
.all([promiseClick3(), promiseClick2(), promiseClick1()])
.then(function(results){
console.log(results);
});
这样以后就可以用all并行执行多个异步操作,并且在一个回调中处理所有的返回数据,比如你需要提前准备好所有数据才渲染页面的时候就可以使用all,执行多个异步操作将所有的数据处理好,再去渲染。
七、promise.race()
all是等所有的异步操作都执行完了再执行then方法,那么race方法就是相反的,谁先执行完成就先执行回调。先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调
我们将上面的方法延迟分别改成234秒
function promiseClick1(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('2s随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('2s数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
function promiseClick2(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('3s随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('3s数字太于10了即将执行失败回调');
}
}, 3000);
})
return p
}
function promiseClick3(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('4s随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('4s数字太于10了即将执行失败回调');
}
}, 4000);
})
return p
}
Promise
.race([promiseClick3(), promiseClick2(), promiseClick1()])
.then(function(results){
console.log('成功',results);
},function(reason){
console.log('失败',reason);
});
race的使用比如可以使用在一个请求在10s内请求成功的话就走then方法,如果10s内没有请求成功的话进入reject回调执行另一个操作。
//请求某个table数据
function requestTableList(){
var p = new Promise((resolve, reject) => {
//去后台请求数据,这里可以是ajax,可以是axios,可以是fetch
resolve(res);
});
return p;
}
//延时函数,用于给请求计时 10s
function timeout(){
var p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时');
}, 10000);
});
return p;
}
Promise.race([requestTableList(), timeout()]).then((data) =>{
//进行成功回调处理
console.log(data);
}).catch((err) => {
// 失败回调处理
console.log(err);
});
请求一个接口数据,10s内请求完成就展示数据,10s内没有请求完成就提示请求失败
这里定义了两个promise,一个去请求数据,一个记时10s,把两个promise丢进race里面赛跑去,如果请求数据先跑完就直接进入.then成功回调,将请求回来的数据进行展示;如果计时先跑完,也就是10s了数据请求还没有成功,就先进入race的失败回调,就提示用户数据请求失败进入.catch回调,(ps:或者进入reject的失败回调,当.then里面没有写reject回调的时候失败回调会直接进入.catch)