文章目录
一、什么是Promise
- Promise 是一种为了避免回调地狱的异步解决方案 (目前最先进的解决方案是async和await的搭配(ES8),它们也是基于promise的);
- 从语法上讲,Promise是一个对象或者说是构造函数,用来封装异步操作并可以获取其成功或失败的结果。
- Promise 是一种状态机制: pending(进行中)、fulfilled(已成功)和rejected(已失败) 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,即Promise的状态改变是不可逆的。
Promise机制说明图:2个阶段,2个过程,3种状态
二、为什么需要Promise
1、Promise出现之前
在ES6之前,对于异步问题的处理一般采用回调函数的形式,但由此产生的回调地狱问题常常影响开发者的开发体验。
2、回调地狱
回调地狱就是回调函数中嵌套回调函数,回调地狱是为了实现代码按顺序执行而出现的一种操作,但层层嵌套往往会造成我们的代码可读性非常差,后期不好维护。
// 回调地狱示例代码
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)
3、Promise标准出现
Promise最早是由社区提出和实现的一种解决异步编程的方案。ES官方参考了大量的异步场景,总结出了一套异步通用的标准模型,该模型可以覆盖几乎所有的异步场景甚至是同步场景,其可概括为2个阶段,2个过程,3种状态,再加上后续处理API(then、catch等),也就是我们所说的Promise。
三、使用Promise优雅的解决回调地狱
错误写法
并不是使用Promise就一定能解决回调地狱,例如下面这个例子,并没有去解决回调地狱,仍然造成了回调地狱
unction 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()
})
}
})
JS执行顺序——宏任务和微任务
Promise属于微任务,如果你对JS执行顺序感兴趣 JavaScript宏任务(macrotask)和 微任务(microtask) 执行顺序
四、async和await
语法糖
async 和 await 是 ES2017 新增两个关键字,它们借鉴了 ES2015 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。
async
目的是简化在函数的返回值中对Promise的创建
async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。
async function test(){
console.log(1);
return 2;
}
//等效于
function test(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
await
await关键字必须出现在async函数中!!!!
await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据。
async function test1(){
console.log(1);
return 2;
}
async function test2(){
const result = await test1();
console.log(result);
}
test2();
等效于
function test1(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
function test2(){
return new Promise((resolve, reject)=>{
test1().then(data => {
const result = data;
console.log(result);
resolve();
})
})
}
test2();
如果await的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行
Tips:
- 只要出现async,一定返回的是Promise,哪怕没有return,它返回的也是Promise,其resolve的就是undefined,在then中能打印出undefined;
- 如果要拒绝,要reject,就使用throw;
- 如果返回的就是Promise对象,会特殊处理,会直接用这个返回的Promise,相当于自动省略了async,没有必要这样操作;
- async中不一定有await,但await关键字一定是只出现在async中,且await必须是直接在async函数返回才有效;
- 有些情况,例如setTimeout,不能直接用async、await,需要先用Promise包装处理;
五、Promise个人总结
- Promise一定要有resolve或者reject,如果不写,就默认resolve()和reject(),即后续的data没有值。原生的Promise如果不resolve/reject就是pending,async就算不return也会默认resolve(undefined)。当然,如果有报错,reject()会自动接收错误并传递给下一环节。
- 无论resolve还是reject,一旦转换就无法改变,即如果Promise内部代码有报错,则后续resolve自动失效。同理,如果已经resolve了,则后续报错也不会传递给下一环节。
- 无论resolve还是reject,都不会中止代码执行,后续代码正常运行。
- Promise内部代码,本身是同步的,只有thenable和catchable代码是异步的。
- 同步resolve/reject,异步return。即只有在第1层new Promise中使用resolve/reject,后续的then中用return/throw。但如果在then中又返回一个new Promise函数并且调用的状态是reject(),就会失败rejected。
- then中的第二个参数,可以替代catch,如果同时写了then的第二个参数和catch,则只执行then的第二个参数,catch不再重复执行。系统默认报错和new Error()报错都能自动被reject()传递,如果没有错误处理函数就会爆红,有就按正常文本提示显示。
- 异步里是没有resolve/reject的,就算加了,也只是外层的,如果外层没有,就会报错。
- 同层级的链式then,会先把全部的第1层执行完,再执行第2层,再执行第3层,解决方法:可将需要后执行的加入后边的then队列中。。。或者可以不解决,只要它们的数据没有关联,先后无所谓(就像轮转时间片)。
- 总的来说,还是async-await好用,特别是await可以直接将异步函数,模拟成一种同步的效果(即一定要拿到await后的值,才执行下一步,即下一步可直接用它的结果)。当然,只是模拟出效果,其实它仍然是异步的。
- await之后的表达式要是Promise,如果不是,就会被Promise.resolve()包装后按规则运行。Promise.resolve(),该方法返回一个resolved状态的Promise,传递的数据作为状态数据。