1. 程式人生 > >這一次,徹底理解Promise原始碼思想

這一次,徹底理解Promise原始碼思想

關於Promise的原始碼實現,網上有太多答案,我也看過很多資料,但都不是很明白。直到有一天我學完函數語言程式設計之函子的概念,才對Promise原始碼有了更深刻的認識。今天,就讓我們來重新認識一下Promise。

我們知道,Promise的誕生是為了解決“回撥地獄”的問題,它用同步鏈式的方式去解決非同步的巢狀回撥。

啥?同步鏈式?這不就是我們上一節學習的函子的思想嗎?如果對函子有所瞭解,那麼再來學習Promise原始碼就比較容易理解了。接下來,我們探究一下函子和Promise有著怎樣的關係。

實現一個簡單的Promise函子

先來回顧一下函子Functor的鏈式呼叫:

class Functor{
       constructor (value) {
          this.value = value ;
       }      
       map (fn) {
         return Functor.of(fn(this.value))
       }
    }

Functor.of = function (val) {
     return new Functor(val);
}

Functor.of(100).map(add1).map(add1).map(minus10)

// var  a = Functor.of(100);
// var  b = a.map(add1);
// var  c = b.map(add1);
// var  d = c.map(minus10);


函子的核心就是:每個函子Functor都是一個新的物件,這個物件的原型鏈上有 map 函式。通過 map 中傳遞進去的函式fn去處理函子儲存的資料,用得到的值去生成新的函子。

等等...函子是同步鏈式,而Promise是非同步鏈式。也就是說上面a的值是非同步產生的,那我們該何如傳入 this.value 值呢?

function executor(resolve){
  setTimeout(()=>{ resolve(100) },500)
}

我們模擬一下通過 setTimeout500 毫秒後拿到資料100。其實也很簡單,我們可以傳進去一個 resolve 回撥函式去處理這個資料。

class Functor {
   constructor (executor) {
      let self = this;
      this.value = undefined;

      // 回撥函式,用來賦值給 value
      function resolve(value){
          self.value = value;
      }
      executor(resolve)
   } 
}

var a = new Functor(executor);

解釋一下上面的程式碼:我們將 executor 傳入並立即執行,在 resolve 回撥函式中我們能夠拿到 value 值,我們定義 resolve 回撥函式將 value 的值賦給 this.value。

這樣我們就輕鬆的完成了 a 這個物件的賦值。由於是非同步得到的,那麼我們怎麼用方法去處理這個資料呢?

根據函子的思想,在拿到資料之後,我們應該讓 map 裡傳入的 fn 函式去處理資料。由於是非同步處理, resolve 執行後才拿到資料,所以我們定義了一個 callback 函式,在 callback 裡面執行 fn。最後把 fn 處理的結果交給下一個函子的 resolve 儲存。

class Functor {
   constructor (executor) {
      let self = this;
      this.value = undefined;
      this.callback = null;
      // 回撥函式,用來賦值給 value
      function resolve(value){
           self.value = value
           self.callback()  // 得到 value 之後,在 callback 裡面執行 map 傳入的 fn 函式處理資料
      }
      executor(resolve)
   } 
  
   map (fn) {
       let  self = this;
       return new Functor((resolve) => {
          self.callback = function(){
              let data =  fn(self.value)   
              resolve(data)
           }
       })
   }    
}

new Functor(executor).map(add1).map(add1)

同時呼叫同一個Promise函子

Promise除了能鏈式呼叫,還能同時呼叫,比如:

var a = new Functor(executor);
var b = a.map(add);
var c = a.map(minus);

像上面這個同時呼叫a這個函子。你會發現,它實際上只執行了c。原因也很簡單,b先給a的 callback 賦值,然後c又給a的 callback 賦值。所以把b給覆蓋掉了就不會執行啦。解決這個問題很簡單,我們只需要讓callback變成一個數組就解決了。

class Functor {
   constructor (executor) {
      let self = this;
      this.value = undefined;
      this.callbacks = [];
      function resolve(value){
          self.value = value;
          self.callbacks.forEach(item => item())
      }
      executor(resolve)
   } 
  
   then (fn) {
       return new MyPromise((resolve) => {
          this.callbacks.push (()=>{
              let data =  fn(this.value) 
              console.log(data)         
              resolve(data)
           })
       })
   }    
}

var a = new MyPromise(executor);
var b = a.then(add).then(minus);
var c = a.then(minus);

我們定義了callbacks陣列,每次的呼叫a的then方法時。都將其存到callbacks陣列中。
當回撥函式拿到值時,在resolve中遍歷執行每個函式。
如果callbacks是空,forEach就不會執行,這也解決了之前把錯的問題
然後我們進一步改了函子的名字為 MyPromise,將map改成then
簡化了return中,let self = this;

增加reject回撥函式

我們都知道,在非同步呼叫的時候,我們往往不能拿到資料,返回一個錯誤的資訊。這一小節,我們對錯誤進行處理。

class MyPromise {
  constructor (executor) {
    let self = this;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    function resolve(value){
      self.value = value;
      self.onResolvedCallbacks.forEach(item => item())
    }
    function reject(reason){
      self.reason = reason;
      self.onRejectedCallbacks.forEach(item => item());
    }
    executor(resolve, reject);
  } 
  then (fn,fn2) {
    return new MyPromise((resolve,reject) => {
      this.onResolvedCallbacks.push (()=>{
        let data =  fn(this.value) 
        console.log(data)         
        resolve(data)
      })
      this.onRejectedCallbacks.push (()=>{
        let reason =  fn2(this.reason) 
        console.log(reason)         
        reject(reason)
      })
    })
  }    
}

其實很簡單,就是我們就是在 executor 多傳遞進去一個 reject
根據非同步執行的結果去判斷執行 resolve,還是 reject
然後我們在 MyPromise 為 reject 定義出和 resolve 同樣的方法
然後我們在 then 的時候應該傳進去兩個引數,fn,fn2

這時候將executor函式封裝到asyncReadFile非同步讀取檔案的函式

function asyncReadFile(url){
  return new MyPromise((resolve,reject) => {
    fs.readFile(url, (err, data) => {
      if(err){ 
         console.log(err)
         reject(err)
      }else {
         resolve(data)
      }
    })
  })
}
var a = asyncReadFile('./data.txt');
a.then(add,mismanage).then(minus,mismanage);

這就是我們平時封裝非同步Promise函式的過程,這個過程有沒有覺得在哪見過。仔細看下,asyncReadFile 不就是前面我們提到的柯里化。

增加Promise狀態

我們定義進行中的狀態為pending
已成功執行後為fulfilled
失敗為rejected

class MyPromise {
  constructor (executor) {
    let self = this;
    this.status = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    function resolve(value){
      if (self.status === 'pending') {
        self.status = 'fulfilled';
        self.value = value;
        self.onResolvedCallbacks.forEach(item => item())
      }
    }
    function reject(reason){
      if (self.status === 'pending') {
        self.status = 'rejected';  
        self.reason = reason;
        self.onRejectedCallbacks.forEach(item => item());
      }
    }
    executor(resolve, reject);
  } 
  then (fn,fn2) {
     return new MyPromise((resolve,reject) => {
      if(this.status === 'pending'){
        this.onResolvedCallbacks.push (()=>{
          let data =  fn(this.value) 
          console.log(data)         
          resolve(data)
        })
        this.onRejectedCallbacks.push (()=>{
          let reason =  fn2(this.reason) 
          console.log(reason)         
          reject(reason)
        })
      }
      if(this.status === 'fulfilled'){
          let x = fn(this.value)
          resolve(x)
      }
      if(this.status === 'rejected'){
          let x = fn2(this.value)
          reject(x)
      }
    })
  }    
}

var a = asyncReadFile('./data.txt');
a.then(add,mismanage).then(add,mismanage).then(add,mismanage);

最後,現在來看傳進去的方法 fn(this.value) ,我們需要用上篇講的Maybe函子去過濾一下。

Maybe函子優化

 then (onResolved,onRejected) {
     
     onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
     onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}

     return new MyPromise((resolve,reject) => {
      if(this.status === 'pending'){
        this.onResolvedCallbacks.push (()=>{
          let x =  onResolved(this.value) 
          resolve(x)
        })
        this.onRejectedCallbacks.push (()=>{
          let x =  onRejected(this.reason)
          reject(x)
        })
      }
      if(this.status === 'fulfilled'){
          let x = onResolved(this.value)
          resolve(x)
      }
      if(this.status === 'rejected'){
          let x = onRejected(this.value)
          reject(x)
      }
    })
  }    

Maybe函子很簡單,對onResolved和onRejected進行一下過濾。

總結

Promise是一個很不好理解的概念,但總歸核心思想還是函子。

同時,在函子的基礎上增加了一些非同步的實現。非同步的實現是一個比較費腦細胞的點,把加粗的字型花點時間多思考思考,加油!

參考連結:函數語言程式設計之Promise的奇幻漂