ES6 Promise详解及源码实现
一.Promise是什么?
Promise是一个构造函数,其实例用于描述异步操作完成的状态和结果。
二.Promise有什么用?
根据上一条,我们可以看出:Promise主要用来处理异步操作(如:接口请求,含setTimeout的操作等) 。
那它和我们之前处理异步任务的方式有什么不同呢?
我们先来看看之前咱们处理异步操作的方式:
//成功回调 function successFunc(res) { //todo console.log(res) } // 失败回调 function failFunc(err) { //todo } //异步操作处理函数 function asyncFunc(successFunc, failFunc) { var a = 1; setTimeout(function (){ if(true) { successFunc(a) }else { failFunc('错误信息') } },5000) } //执行异步操作(成功和失败的回调函数时以参数传进去的) asyncFunc(successFunc, failFunc);
通过上述代码我们可以看出:异步操作的成功和失败的处理函数是提前定义好,然后以参数的形式传入的,当异步操作结果确定之后,根据异步操作的结果调用对应的回调函数。
假如异步操作的回调函数中还有异步任务,就会出现回调中再出现异步操作,异步操作中又有自己的回调,如果这样的嵌套操作过多,就出现了我们常说的【回调地狱】。代码的可读性和可维护性将大大降低。
咱们再来看看如何用Promise来处理异步操作的
new Promise((resolve,reject) => { var a = 1; setTimeout(function(){ if(true) { resolve(a) }else { reject('错误信息') } },1000) }).then(res=> { // todo console.log(res) }).catch(err => { // todo });
通过上述代码可以看出:我们是用同步思维方式编写异步操作的代码。
假如成功处理函数中还有异步操作,使用Promsie实现代码如下:
new Promise((resolve,reject) => { var a = 1; setTimeout(function(){ if(true) { resolve(a) }else { reject('错误信息') } },1000) }).then(res=> { // todo return new Promise((resolve,reject) => { setTimeout(function(){ resolve(2) },1000) // 该异步操作将动态插入调用链中,所以他的成功处理函数时后边的then }) }).then(res=> { console.log(res) // 2 }).catch(err => { // todo });
三.Promise中经常使用的点
1. Promise实例化对象有3个状态:pending(待定)、fulfilled(已完成)、rejected(已拒绝)
// -------------pending--------------- console.log(new Promise((resolve,reject) =>{ resolve(1) }).then(res => { console.log(res) return res; })); // Promise{<pending>} //----------fulfilled----------------- var a = new Promise((resolve,reject) => { resolve(1); }) console.log(a) // Promsise {<fulfilled>: 1} //------------rejected----------------- var a = new Promise((resolve,reject) => { reject(1); }) console.log(a) // Promsise {<reject>: 1}
2. then、catch
var a = new Promise((resolve,reject) => { // 异步操作 setTimeout(()=> { resolve(1); },1000) }).then(res => { console.log(1) return 5; // then中函数的return出去的值(Promise实例除外)将作为Promise实例对象的值,同时也会传到下个then里边的函数中 }).catch(e => { // todo }).then(res => { console.log(res); // 5 }); //------------或者将catch中的函数放在then的第二个参数中------------- var a = new Promise((resolve,reject) => { resolve(1); }).then(res => { console.log(1) }, e => { // todo });
3. 静态方法:Promise.resolve(x)
作用:返回一个状态为fulfilled,值为x的Promise实例
Promise.resolve(1); // // Promsise {<fulfilled>: 1} //----------对状态为fulfilled的Promise实例,也可以调用then var a = Promise.resolve(1); a.then(res => { console.log(res); // 1 return 2; });
4. 静态方法:Promise.reject(x)
作用:返回一个状态为rejected,值为x的Promise实例
Promise.reject(1); // // Promsise {<rejected>: 1} //----------对状态为rejected的Promise实例,也可以调用catch var a = Promise.reject(1); a.then(res => { }).catch(e => { console.log(e) // 1 });
5. 静态方法:Promise.all(iterable)
将多个Promise实例包装到一起,当iterable中所有Promise对象状态都为fulfilled时,则执行Promsie.all()后的then中的函数,值为每项的结果的集合。 一旦有其中某项状态为rejected时,则立即执行Promsie.all()后的catch中的函数
//-----------fulfilled---------- var promise1 = new Promise((resolve,reject) => { setTimeout(()=> { resolve(1); },1000) }) var promise2 = new Promise((resolve,reject) => { setTimeout(()=> { resolve(2); },2000) }) Promise.all([promise1 ,promise2]).then(res => { console.log(res) // 2s之后打印[1,2] }) //-----------rejected---------- var promise1 = new Promise((resolve,reject) => { setTimeout(()=> { reject(1); },1000) }) var promise2 = new Promise((resolve,reject) => { setTimeout(()=> { resolve(2); },2000) }) Promise.all([promise1 ,promise2]).then(res => { }).catch(e => { console.log(e); // 1s后打印1 })
6. 静态方法:Promise.any(iterable)(还在草案阶段,浏览器暂不支持)
将多个Promise实例包装到一起,当iterable中任意一个Promise对象状态变为fulfilled时,则执行Promsie.any()后的then中的函数,值为那一项的值。当iterable中所有的Promise对象状态变为rejected时
7. Promise.race(iterable)
将多个Promise实例包装到一起,取iterable中最先改变状态的Promise对象,若状态为fulfilled则执行Promsie.race()后的then中的函数,值为那一项的值。若状态为rejected则执行Promsie.race()后的catch中的函数,值为那一项的值。
8. finally
上一个操作完成,无论是resolve还是reject,finally中的函数都会执行。注意:finally中的函数只处理逻辑,不影响Promise实例本身状态和值
new Promise((resolve,reject)=>{ resolve(1) }).finally(() => { return 2 }).then(res => { console.log(res) // 1 })
四.Promise中重难点和易忽略点
- resolve和then中的返回值若是Promise对象时,该Promise对象动态插入链式调用中。eg:
new Promise((resolve,reject)=>{ resolve(Promise.resolve(1)) }).then(res => { return Promise.resolve(1).then(res => { console.log(1) }).then(res => { console.log(2) }); }).then(res => { console.log(3) }) // 1 2 3
- then()中参数不是函数时,链式调用就直接进行下个环节的操作。(源码参见Promise then)
Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .then(res => { console.log(res) // 1 })
- catch语句本质是捕获程序中的错误(reject会转换成程序错误),程序报错未被捕获时,会一直向后传递,直到捕获为止
//---------------捕获reject-------------- new Promise((resolve,reject)=>{ reject(1) }).then(res => { }).catch(e=>{ console.log(e) // 1 }) //---------------捕获代码问题-------------- new Promise((resolve,reject)=>{ console.log(lalaa) resolve(1) }).then(res => { }).catch(e=>{ console.log(e) //ReferenceError: lalaa is not defined... })
// -----------最后一个catch可以捕获之前的所有错误------------------------
new Promise((resolve,reject)=>{
reject(1)
}).then(res => {
}).then(res => {
}).then(res => {
}).then(res => {
}).catch(e=>{
console.log(e) // 1
})
- resolve()后的代码会按同步方式执行,then中传递的函数return **之后代码不执行
new Promise((resolve, reject) => { resolve(); console.log(111); }) .then(() => { consle.log(222); return 3; consle.log(333); }) // 111 222
五.自定义实现Promise—then
- 用类实现源码
class YelPromise { //构造函数主体 constructor (mainFunc) { // 属性初始化 Object.assign(this,{ status: "pending",// YelPromise的执行状态 value : undefined,// YelPromise的值 successArr : [], // 成功回调数组(then中的函数组成的队列) }) mainFunc(this.resolve.bind(this)); } // 原型上的resolve方法 resolve (resolveValue){ let doNextfunc = function (self,nextValue) { // 使用异步任务的原因:调用resolve方法时,还没执行then函数,successArr为空 // 注意:这个地方使用的setTimeout来模拟实现,在Promise中实际使用的是微任务的异步方式 setTimeout(() =>{ let isYelPromise = function (val){ // 若返回值为YelPromise实例,则将该实例动态插入执行队列中 if(val instanceof YelPromise) { self.successArr.unshift(...val.successArr); val.successArr = []; // 使用setTimeout是为了取到val.value setTimeout(()=>{ self.resolve(val.value); },0) return self; } else { return false; } }; if(isYelPromise(nextValue)) { return; } // 先进先出原则,取出第一个成功回调函数并执行 let successHeadFunc = self.successArr.shift(); if(successHeadFunc) { let result = successHeadFunc(nextValue); if(isYelPromise(result)) { return; } // 根据执行结果,更改promise实例的value值(真实的Promise中的then会 // 判断传入的参数是否为函数,如果不是,则跳过,实例的value不变) if(self.successArr && self.successArr.length){ self.value = result; doNextfunc(self,result) // 继续处理下个then中的函数 } else { returnResult (self,result) } } else { returnResult (self,nextValue) } },0) }; // 处理完成,更改状态 let returnResult = function (self,finallyValue){ self.status = 'fulfilled'; self.value = finallyValue; } // 执行then中传递的函数 doNextfunc(this,resolveValue); return this; // 返回当前对象,方便链式调用 } // 原型上的then方法 then (func){ // 将then中传递的方法放入successArr,后续按先进先出的原则调用 this.successArr.push(func); // 结果已确定,程序不会再从successArr中取函数执行,故需要手动调用resovle方法 if(this.status == 'fulfilled') { this.resolve(this.value) } return this; // 方便链式调用 } }
- 用构造函数实现源码
// resolve方法 YelPromise.prototype.resolve = function(resolveValue) { let doNextfunc = function (self,nextValue) { // 使用异步任务的原因:调用resolve方法时,还没执行then函数,successArr为空 // 注意:这个地方使用的setTimeout来模拟实现,在Promise中实际使用的是微任务的异步方式 setTimeout(() =>{ let isYelPromise = function (val){ // 若返回值为YelPromise实例,则将该实例动态插入执行队列中 if(val instanceof YelPromise) { self.successArr.unshift(...val.successArr); val.successArr = []; // 使用setTimeout是为了取到val.value setTimeout(()=>{ self.resolve(val.value); },0) return self; } else { return false; } }; if(isYelPromise(nextValue)) { return; } // 先进先出原则,取出第一个成功回调函数并执行 let successHeadFunc = self.successArr.shift(); if(successHeadFunc) { let result = successHeadFunc(nextValue); if(isYelPromise(result)) { return; } // 根据执行结果,更改promise实例的value值(真实的Promise中的then会 // 判断传入的参数是否为函数,如果不是,则跳过,实例的value不变) if(self.successArr && self.successArr.length){ self.value = result; doNextfunc(self,result) // 继续处理下个then中的函数 } else { returnResult (self,result) } } else { returnResult (self,nextValue) } },0) }; // 处理完成,更改状态 let returnResult = function (self,finallyValue){ self.status = 'fulfilled'; self.value = finallyValue; } // 执行then中传递的函数 doNextfunc(this,resolveValue); return this; // 返回当前对象,方便链式调用 }; // then方法 YelPromise.prototype.then = function(func) { // 将then中传递的方法放入successArr,后续按先进先出的原则调用 this.successArr.push(func); // 结果已确定,程序不会再从successArr中取函数执行,故需要手动调用resovle方法 if(this.status == 'fulfilled') { this.resolve(this.value) } return this; // 方便链式调用 }; //构造函数主体 function YelPromise(mainFunc) { // 属性初始化 Object.assign(this,{ status: "pending",// YelPromise的执行状态 value : undefined,// YelPromise的值 successArr : [], // 成功回调数组(then中的函数组成的队列) }) mainFunc(this.resolve.bind(this), this.resolve.bind(this)); }
- 测试代码:
var a = new YelPromise((resolve,reject)=> { resolve(1) }).then(res=> { return new YelPromise((resolve,reject)=> { resolve() }).then(res=> { console.log(1) }).then(res=> { console.log(2) }) }).then(res=>{ console.log(3) }).then(res=>{ console.log(4) })
//---------------捕获代码问题----