1、Promise是什么
Promise是异步编程的一种解决方案。Promise本身是一个对象(一个构造函数),从它可以获取异步操作信息。主要处理回调地狱、异步的问题。
Promise有三种状态,pending(等待状态),fulfilled(成功状态),rejected(失败状态)。
Promise还有state属性和result属性,state值是当前状态值,result值是当前结果值。
新建一个promise实例:
此时,可以看到Promise是一个对象,里面有PromiseState和PromiseResult属性,state属性的value值是当前promise的状态(pending),result的值是当前promise的结果值。
可以看到new Promise 的时候,有两个参数,resolve和reject,这两个参数是两个函数,resolve是promise成功时执行的函数(此时promise状态变为fulfilled),reject是promise失败是执行的函数(此时promise状态变为rejected)。并且当promise的状态发生改变之后,promise状态不会再发生改变(可自行多添加几个resolve或reject测试)。当执行resolve和reject时,下面可以看到promise的状态变化。
此时promise状态变为rejected,提示警告,未捕获到这个失败。
总结:promise的三种状态:pending状态 fulfilled状态 rejected状态
创建promise时,此promise状态为pending,他的result值为undefined
当调用resolve或reject时,promise的状态发生改变
resolve('成功') 此时promise状态为fulfilled,result值为resolve返回值就是'成功'
reject('失败') 此时promise状态为rejected,result值为reject返回值就是'失败'
*promise的状态一经改变,就不会再发生改变
2、Promised中的.then(),.catch(),.finally()方法
上面我们看到,当promise状态为rejected时,有一条警告,提示未捕获到'失败'在promise中,下面开始介绍Promise的.then()方法,用于捕获警告和抛出的错误。
*当promise状态发生改变的时候,就会执行.then()方法,相反,状态没有发生改变的时候.then()就不会执行
(1).then()
.then((value)=>{console.log(value)},(error)=>{console.log(error)})
可以看出,.then()方法传两个参数,分别是两个回调函数,第一个是promise状态变为fulfilled时执行的回调,第二个时状态变为rejected时执行的函数,value值是成功、失败的结果值。
const myPromise = new Promise((resolve, reject) => {
console.log('promise实例');
resolve('成功')
// reject('失败')
})
console.log(myPromise);
myPromise.then((value) => {
console.log('value',value);
},
(error) => {
console.log(error);
})
结果:
当放开rejected,注释调resolve时。此时可以看到没有那条警告了,.then()的第二个参数捕获到reject的抛出的错误警告,并获取到返回值,也就时error的值。
另外:promise的这些方法也是可以进行链式调用的,比如:
const myPromise = new Promise((resolve, reject) => {
console.log('promise实例');
resolve('成功')
// reject('失败')
})
console.log(myPromise);
myPromise.then((value) => {
console.log('value', value);
return 2
},
(error) => {
console.log(error);
}).then((value) => {
console.log(value);
})
结果:
此时,.then()里面的回调return一个值的时候,这个值不是promise对象,就会自动将这个值包裹成promise对象,例:return 2 --> return Promise.resolve('2')
.then()和.catch()的return都会返回一个新的promise对象
。返回结果是一样的。.catch中也是如此。但是如果抛出错误时,需要return Promsie.reject('3')或者 throw new Error('3')。此时可自行打印查看结果,下面会提到:
(2).catch()
.catch((error)=>{console.log('error',error)})
它的作用是,可以捕获上层未能捕获到的错误,
例:
const myPromise = new Promise((resolve, reject) => {
console.log('promise实例');
// resolve('成功')
reject('失败')
})
console.log(myPromise);
myPromise.then((value) => {
console.log('value', value);
return 2
}, ).then((value) => {
console.log(value);
}).catch((error) => {
console.log('error', error);
})
此时,我们看,第一次.then()时,没有传递第二个参数,所以未捕获错误,第二次.then()同样没有传递第二个参数,也未捕获错误,在.catch的时候,捕获到错误。
此时我们看一下如何进行抛出错误:
const myPromise = new Promise((resolve, reject) => {
console.log('promise实例');
resolve('成功')
// reject('失败')
})
console.log(myPromise);
myPromise.then((value) => {
console.log('value', value);
return new Error('11')
// throw new Error('11')
// return Promise.reject('11')
}, ).then((value) => {
console.log(value);
}).catch((error) => {
console.log('error', error);
})
第9行,第10行,第11行,
第9行结果:
第10、11行结果:(在.catch()中捕获到错误,并打印error。印证上述黄色背景描述结论)
(3).finally()
.finally(()=>{console.log('222')})
.finally()方法,不需要传递参数,只要promise状态改变的时候,就会执行,无论状态变为fulfilled还是rejected。
例:
const myPromise = new Promise((resolve, reject) => {
console.log('promise实例');
resolve('成功')
// reject('失败')
})
console.log(myPromise);
myPromise.then((value) => {
console.log('value', value);
return 1
}, ).then((value) => {
console.log(value);
}).catch((error) => {
console.log('error', error);
}).finally(() => {
console.log('something');
})
在实际应用中可能会碰到一些上述没总结到的情况,请评论。
3、Promise中的all()和race()方法
共同点,两个方法都是接收一组异步任务,并行执行异步任务,
不同点:.all():在所有异步任务执行完成之后在执行回调,(异步任务全部完成,会在一个回调里处理全部数据,如果,异步任务中,有一个执行失败,那么就不会执行这个处理结果的回调)
.race():只保留第一个异步任务完成的结果,其他异步任务仍然执行,但是结果不保留,(不会放在成功的回调里。)
例:
//函数1
function runAsync(x) {
let p = new Promise((resolve) => {
setTimeout(() => {
resolve(x)
console.log(x);
}, 1000);
})
return p
}
//失败函数 函数2
function runReject(x) {
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject(x)
console.log(x);
}, 1000 * x);
})
return p
}
(1).all()
执行多个函数1时
Promise.all([runAsync(1), runAsync(2), runAsync(3), runAsync(4)]).then((res) => {
console.log('res', res);
结果:同时打印1234和res
如果:异步任务中出现失败的情况,(添加runReject函数)
Promise.all([runAsync(1), runReject(2), runAsync(3), runReject(4)]).then((res) => {
console.log('res', res);
})
结果:1、3同时打印,2秒后打印2,报错,4秒后打印4
2秒的时候,报错,此时我们添加.catch捕获异常
Promise.all([runAsync(1), runReject(2), runAsync(3), runReject(4)]).then((res) => {
console.log('res', res);
}).catch((err) => {
console.log('err', err);
})
结果:顺序和上面相同,报错被捕获到。但是下面的4的错误为什么没有被捕获到呢?
总结:.catch()函数能够捕获到.all()里最先的那个异常,并且只执行一次。
而且此时,没有了那个res的返回值。没有执行.then()的第一个回调。
(2).race()
执行多个函数1时
Promise.race([runAsync(1), runAsync(2), runAsync(3), runAsync(4)]).then((res) => {
console.log('res', res);
}).catch((err) => {
console.log('err', err);
})
结果:1s后同时打印,但是,只有1个是从.then()中返回的。所以他是只保留第一个执行完成的结果,其他的异步仍然执行,但是,不会保留在执行成功的回调里了。
当异步任务中出现失败的情况,我们看下:
Promise.race([runAsync(1), runReject(2), runAsync(3), runReject(0)]).then((res) => {
console.log('res', res);
}).catch((err) => {
console.log('err', err);
})
结果:0秒打印0 和.catch里面的返回值,1s后,打印1、3,2s后打印2
总结:all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行
(3).allSettled方法
接受一个promise数组作为参数,同时开始,并行执行。
但是:.allSettled方法不会走进.catch,当所有的promise执行完成之后,无论成功或者失败,都会解析出一个具有完整状态的数组。
.all是:(同时开始,并行执行)数组全部执行完成,才会统一在一个回调里面返回值,如果有一个是失败状态,则没有返回值。
.race:数组全部执行,最先执行完成的那个(且状态是成功),执行一次回调函数,该回调是最先执行完成的值,其他数组异步继续执行。
4、事件循环、宏任务、微任务之间的顺序和关联
首先,宏任务和微任务都是异步任务
一段代码执行的时候,先执行同步任务,再执行宏任务,再执行微任务
队列:供任务队列和微任务队列
微任务队列为空的时候才会进入下一次循环,即执行下一个宏任务,依次循环,直到宏任务、微任务列表都为空,执行完毕。(宏任务、微任务中遇到同步任务时,肯定先执行同步任务)
常见的宏任务:script、setTimeout、setInterval、setImmediate 、I/O 、UI rendering
常见的微任务:Promise.then()或catch(),MutationObserver,Node独有的process.nextTick,
事件循环执行顺序
最开始整个脚本(script标签)作为一个宏任务执行
执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
同步代码执行完毕后,执行栈为空,(因为第一个宏任务是script,第一次的宏任务已经执行完毕)开始执行检查微任务队列,执行微任务,直到微任务列表为空,再执行下一个宏任务。依次循环,直到宏任务队列和微任务队列为空。
例1:
console.log('11');
const myPromise = new Promise((resolve, reject) => {
console.log('12');
resolve('13')
})
console.log('14');
myPromise.then((value) => {
console.log('15');
console.log('value', value);
})
console.log('16');
结果:
详细讲解:脚本作为第一个执行文件开始执行,他是一个宏任务,此时宏任务队列:宏:【script】
首先打印11
然后12 (new Promise内部是一个立即执行函数,可以当做为同步任务,所以立即打印)
然后执行resolve(‘13’),此时promise状态改变,
跳出new Promise构造函数,
打印14
遇到.then(),他是一个微任务,进入微任务队列,此时的微任务队列:微:【.then()】
打印16
此时,第一轮的同步任务执行完毕,第一轮的宏任务(script)也执行完毕,开始检查微任务队列,有一个.then(),执行函数,打印15,然后打印13,检查宏任务、微任务列表,都为空,事件循环执行完毕。
例2:
console.log('11');
const myPromise = new Promise((resolve, reject) => {
console.log('12');
setTimeout(() => {
resolve('13')
console.log('21');
}, 0)
console.log('22');
})
console.log('14');
myPromise.then((value) => {
console.log('value', value);
return 5
}).then((value) => {
console.log('thenvalue', value);
setTimeout(() => {
console.log('settimeout');
}, 0)
})
console.log('16');
结果:
这个,外面的顺序和第一个差不多,,就是新添加了setTimeout,和链式调用了.then(),
大家可以自行判断答案,如有问题,牢记(微任务队列为空的时候,才会进入宏任务队列执行下一个宏任务,也就是说,一个宏任务可能后面跟好几个微任务队列)。
5、附言
本文也是总结出来的,经过判断和验证,应该没有问题,如有问题,请直接提出来!事件循环的内容实际还很多,这里大概解释了过程,用于判断执行先后顺序完全可以。加油~