Promise学习
如果你还没有了解同步异步是什么,请先学习上一篇文章
https://blog.nowcoder.net/n/dd156579f7b8463b992e264ba7ab72da
Promise是为了解决什么问题:
1、主要用于异步计算
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
3、可以在对象之间传递和操作promise,帮助我们处理队列
以前我们解决异步回调是通过函数的嵌套调用,这样剥夺了函数return的能力,如果嵌套层次多了,比如为了完成一个功能,我们需要调用api1、api2、api3,依次按顺序调用,这个时候就会存在回调地狱的问题,使得代码非常之不优雅,可读性很差,维护困难,promise就是为了解决回调地狱,让代码看起来更加优雅、简洁。
Promise的特点:
- Promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
- Promise没有剥夺函数return的能力,因此无需层层传递callback进行回调获取数据
- Promise代码风格容易理解,便于维护
- Promise方便解决多个异步等待合并
- Promise一共有三个状态:准备状态(pending)、解决状态(fulfilled)、拒绝状态(rejected)当promise状态发生改变,会触发then()方法里面的响应函数处理后续步骤,promise状态一经改变,就不会再变了(状态凝固)。
- Promise会产生微任务
Promise微任务处理机制:
例:比方说我今天要去吃肯德基,一种方式是有很多人排队,我需要等我的餐做好之后再拿餐走人,如果做的时间需要十分钟,我就需要站着排队等十分钟后才能走,这种情况就需要等待一件事情做完以后才能继续做下一件事情,非常不方便;另一种方式是我点餐(promise)以后可以拿号去找个桌子坐下,等到餐做好了叫号通知我已经做完了(resolve),可能这个时候我正在打电话,等我打完电话再过去拿餐(等待同步任务执行完毕再处理任务队列),或者服务员说没食材了这个餐做不了(reject),我可以选择走人去隔壁吃个必胜客啥的,显然这种方式更加省事,不用站在那一直傻等着。
resolve和reject都会创建一个微任务,微任务可以创建多个,resolve创建的微任务使用.then接收,reject创建的微任务使用.catch接收
promise队列原理:
队列中的每一个成员都要是一个promise,下一个成员必须等到上一个成员状态发生改变才会执行。
举例:使用map(别的循环方法也可以)实现promise队列:
function fn(num) { let promise = Promise.resolve(); num.map(v => { promise = promise.then(_ => { return new Promise((resolve => { setTimeout(() => { console.log(v); resolve(); // 上一个成员状态改变后下一个成员才会执行 }, 1000) })) }) }) } fn([1,2,3,4,5]);
参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
宏任务和微任务的执行顺序:
promise执行会生成微任务,setInterval | setTimeout生成宏任务,任务队列会先执行微任务,后执行宏任务,比方家里有大人有小孩,大人饿了可以先忍一忍,要先把小孩的奶喂了,这是一种执行机制,宏任务和微任务都属于异步,需要等同步代码执行完毕后才会运行,类似于你去银行办业务让vip客户先办,你得等vip客户办完之后才能办,此时,同步代码就是vip客户。
// 宏任务 setTimeout(() => { console.log('setTimeout') }, 0) // 微任务 new Promise((resolve, reject) => { resolve(1); console.log('promise'); // 同步代码 }).then((value) => { console.log(value) }) console.log('同步'); // 同步代码
输出结果:
思考题:输出的先后顺序是什么?
// 微任务 new Promise((resolve, reject) => { // 宏任务 setTimeout(() => { console.log('setTimeout') resolve(1); }, 0) console.log('promise'); // 同步代码 }).then((value) => { console.log(value) }) console.log('同步'); // 同步代码
解题:① promise ② 同步 ③ setTimeout ④ 1
先执行同步任务输出‘promise’跟‘同步’,由于微任务的创建是在宏任务中执行的,此时宏任务执行前微任务还没有被创建,因此会先把宏任务拿到主线程执行输出'setTimeout',然后再把resolve创建的微任务存入任务队列中,等到当前宏任务(setTimeout)执行完毕后再将微任务拿到主线程当中执行。
可以验证,下面代码的运行结果跟上面是一样的
// 微任务 new Promise((resolve, reject) => { // 宏任务 setTimeout(() => { resolve(1); console.log('setTimeout') }, 0) console.log('promise'); // 同步代码 }).then((value) => { console.log(value) }) console.log('同步'); // 同步代码
Promise单一状态与状态中转:
只有当改变状态的时候才会产生微任务,如果状态改变的时间比较晚,微任务产生的时间也会比较晚。
new Promise((resolve, reject) => { resolve('成功'); // 改变状态产生微任务 }).then(msg => { // 处理微任务 console.log(msg); }) console.log('同步'); ------------------------------------------------------------------ new Promise((resolve, reject) => { setTimeout(() => { resolve('成功'); // 延迟两秒后才改变状态产生微任务 }, 2000) }).then(msg => { // 会等到微任务产生后才处理微任务 console.log(msg); // 两秒后输出'成功' }) console.log('同步'); ------------------------------------------------------------------ let p1 = new Promise((resolve, reject) => { resolve('成功'); // 改变状态产生微任务 }) new Promise((resolve, reject) => { resolve(p1); // 将另一个promise状态传递 }).then(msg => { // 处理微任务 console.log(msg); // 输出'成功' }) console.log('同步'); ------------------------------------------------------------------ let p1 = new Promise((resolve, reject) => { resolve('成功'); // 改变状态产生微任务 }) new Promise((resolve, reject) => { setTimeout(() => { resolve(p1); // 延迟两秒后将另一个promise状态传递 }, 2000) }).then(msg => { // 处理微任务 console.log(msg); // 延迟两秒后输出'成功' }) console.log('同步');
promise状态改变后微任务就已经产生了,是不可逆的,也就是说不可以再撤销,比如你去点奶茶,奶茶做好了就不能撤销了,喝就完事了。
let p1 = new Promise((resolve, reject) => { reject('失败'); // 产生失败微任务 }) new Promise((resolve, reject) => { // 状态已经在p1产生了,在当前promise中使用resolve也不可以重新改变状态 resolve(p1); }).then(msg => { // 处理微任务 console.log(msg); }, err => { console.log('err' + err); // 走失败微任务处理程序,输出'err失败' }) console.log('同步');
Promise.then也是一个promise:
let p1 = new Promise((resolve, reject) => { resolve('success'); }) // .then执行完后会返回promise对象 let p2 = p1.then(msg => {}, err => {}); console.log(p1); console.log(p2);
输出结果:
因此.then后面的链式.then都是对上一个返回的promise的处理
let p1 = new Promise((resolve, reject) => { resolve('success'); }) let p2 = p1 .then( msg => console.log(msg), // success err => console.log(err) ) .then( a => console.log('成功'), // 成功 b => console.log(b) )
输出结果:
新生成的promise( p1.then() )默认返回的状态就是成功,即使p1中的状态改成reject,p1.then().then()还是会执行成功任务
let p1 = new Promise((resolve, reject) => { reject('fail'); }) let p2 = p1 .then( msg => console.log(msg), err => console.log(err) // fail ) .then( a => console.log('成功'), // 成功 b => console.log(b) )
输出结果:
通过给上一个.then返回(return)新的promise,可以改变下一个.then对它的处理方式
let p1 = new Promise((resolve, reject) => { resolve('success'); }) let p2 = p1 .then( msg => { return new Promise((resolve, reject) => { reject('处理失败') }) }, err => console.log(err) ) .then( a => console.log('success ' + a), // 此时处理的状态是上一个.then中return的promise的状态 b => console.log('fail ' + b) // fail 处理失败 )
输出结果:
promise其他类型的写法:如果一个对象或者类中包含then方法,js会自动包装成promise
let p1 = new Promise((resolve, reject) => { resolve('success'); }) let p2 = p1 .then( msg => { // 1、直接返回一个对象,只要其中有then方法,就会被解析为promise return { then(resolve, reject) { resolve('成功'); } } }, err => console.log(err) ) .then( a => console.log(a), // 成功 b => console.log(b) ) ------------------------------------------------------------------- let p1 = new Promise((resolve, reject) => { resolve('success'); }) let p2 = p1 .then( msg => { // 2、返回构造函数实例 class R { then(resolve, reject) { resolve('成功'); } } return new R(); }, err => console.log(err) ) .then( a => console.log(a), // 成功 b => console.log(b) ) ------------------------------------------------------------------- let p1 = new Promise((resolve, reject) => { resolve('success'); }) let p2 = p1 .then( msg => { // 3、返回静态方法 return class { static then(resolve, reject) { resolve('我是静态方法') } } }, err => console.log(err) ) .then( a => console.log(a), // 我是静态方法 b => console.log(b) )
Promise.catch用法:
catch可以统一处理前面所有的then抛出的错误状态或者是其他错误,推荐放在最后调用,如果前面的.then中写了处理reject的回调,优先执行回调。使用catch就不用每个.then中都处理reject了
let p1 = new Promise((resolve, reject) => { resolve('success'); }) let p2 = p1 .then( msg => { return new Promise((resolve, reject) => { reject('reject'); }) }) .then( a => console.log(a), ) .catch( err => console.log(err) // reject )