1. 程式人生 > 實用技巧 >ES6 Promise詳解及原始碼實現

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)
}) 

//---------------捕獲程式碼問題----