前言
随着JavaScript的发展,对异步操作的代码变的越来越简单。由起初的回调函数,到ES6新增的Promise,且支持链式调用解决回调地狱的问题,到现在大家耳熟能详的回调地狱的终极解决方法async、await。
这篇只详细介绍一下Promise吧!!!
一、promise入门
抽象表达:
Promise是JS进行异步编程的新的解决方案
具体表达:
从语法上来说:Promise是一个构造函数
从功能上来说:Promise用来封装一个异步操作并且可以获取
使用:
Promise接受一个函数作为参数,该函数的两个参数分别是resolve和reject,这两个参数都是回调函数,并且返回一个新的Promise对象。新的Promise对象的执行结果状态由resolve和reject,决定。如果返回的是resolve,新的Promise变为resolve,value为返回的值;如果返回的是reject,新的Promise变为reject,reason为抛出的异常。通过添加.then()去接收新的Promise对象的状态结果。
// 成功的Promise
const p1 = new Promise(((resolve, reject) => {
resolve('我是成功的Promise值')
}))
p1.then(value=>{
console.log(value) // 输出 '我是成功的Promise值'
},reason => {
console.log(reason)
})
// 失败的Promise
const p2 = new Promise(((resolve, reject) => {
reject('我是失败的Promise值')
}))
p2.then(value=>{
console.log(value)
},reason => {
console.log(reason)// 输出 '我是失败的Promise值'
})
状态:
Promise对象有三种状态:pending(初始化状态)、fulfilled(成功的状态)、reject(失败的状态)
状态只能从 pending => fulfilled 或 pending => reject
状态一旦确定就不能在改变了
二、Promise入坑
1.Promise的错误捕获
// reject捕获
const p2 = new Promise(((resolve, reject) => {
reject('我是失败的Promise值')
}))
p2.then(value=>{
console.log(value)
},reason => {
console.log(reason) // 输出 '我是失败的Promise值'
})
// catch捕获
const p2 = new Promise(((resolve, reject) => {
reject('我是失败的Promise值')
}))
p2.then(value=>{
console.log(value)
}).catch(reason=>{
console.log(reason) // 输出 '我是失败的Promise值'
})
从上面代码可以看出reject回调和catch都可以捕获错误,没有出现链式调用的时候都可以很完美的捕获抛出异常,但是如果是链式调用的Promise应该使用哪个呢?我们正常写代码逻辑应该就是捕获到异常就抛出异常并让代码停止,不再继续向下执行了。我们来测试一下
// 还是上面那个失败的案例,我们只调用then两次,结果一样。
// reject捕获
const p2 = new Promise(((resolve, reject) => {
reject('我是失败的Promise值')
}))
p2.then(
value=>{
console.log('我是value' + value)
},reason=>{
console.log('我是reason' + reason)
}
).then(
value1=>{
console.log('我是value1' + value1)
},reason1=>{
console.log('我是reason1' + reason1)
}
)
结果输出:
我是reason:我是失败的Promise值
我是value1:undefined
// catch捕获
const p2 = new Promise(((resolve, reject) => {
reject('我是失败的Promise值')
}))
p2.then(
value=>{
console.log('我是value:' + value)
}
).then(
value1=>{
console.log('我是value1:' + value1)
}
).catch(reason=>{
console.log('出错了:' + reason)
})
结果输出:
出错了:我是失败的Promise值
不知道和大家想的结果是否一致:Promise官方规定,如果一个错误在reject中被捕获处理了,那么Promise的状态将恢复过来,
这就意味着下一个then方法将接收到一个成功的Promise,这样看来结果就是对的了吧。所以当我们链式调用的时候使用catch
来捕获异常还是比较合理的!(但是catch要放在then的最后,不然catch下面的then方法还是会执行,且返回一个成功的回调。不过这似乎不台符合我们的编码习惯,哈哈)
2.事件循环中的Promise
简单介绍一下事件循环:
当同步代码执行完成后,就会开启事件循环(EventLoop),不断去循环回调队列,看队列中是否有回调函数需要被执行。若队列中有回调函数需要执行,且当前调用栈为空,则将该回调函数推入到调用栈并执行;若调用过程中发现回调函数中有内嵌的回调函数,则继续将回调函数丢入回调队列中,等当前的回调调用完成后,再开启新一轮的回调函数调用。
事件循环中的三个队列:调用栈、微任务队列、宏任务队列
调用栈: 存放并执行同步代码
微任务队列: 存放微任务的队列(Promise、async和await、nextTick…)
宏任务队列: 存放宏任务的队列(定时器、fetch…)
执行顺序: 同步代码 > 微任务 > 宏任务
下面的实例是个大厂的面试题:上就上最难的
// 下面代码的输出顺序是什么?
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
});
console.log('script end')
结果:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
不知道大家感觉怎么样,说实话这道题在Promise相关的题中并不算太难,大家好好分析下。
有个比较希望大家注意的点:nextTick函数返回的也是一个Promise对象,也会被放入微任务队列,但是它总是比Promise对象返回的微任务先执行,这个问题先留给大家。