ES6 Promise詳解及原始碼實現
一.Promise是什麼?
Promise是一個建構函式,其例項用於描述非同步操作完成的狀態和結果。
二.Promise有什麼用?
根據上一條,我們可以看出:Promise主要用來處理非同步操作(如:介面請求,含setTimeout的操作等) 。
那它和我們之前處理非同步任務的方式有什麼不同呢?
我們先來看看之前咱們處理非同步操作的方式:
//成功回撥 function successFunc(res) { //todo console.log(res) } // 失敗回撥 function failFunc(err) { //todo } //非同步操作處理函式 functionasyncFunc(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) })
//---------------捕獲程式碼問題----