這一次,徹底理解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 MyPromise {
   constructor (executor) {
      let self = this;
      this.value = undefined;

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

var a = new MyPromise(executor);

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

這樣我們就輕松的完成了 a 這個對象的賦值。由於是異步得到的,那么我們怎么用方法去處理這個數據呢?

根據函子的思想,在拿到數據之后,我們應該讓 map 里傳入的 fn 函數去處理數據。由於是異步處理, resolve 執行后才拿到數據,所以我們定義了一個 callback 函數,在 callback 里面執行 fn。最后把 fn 處理的結果交給下一個函子的 resolve 保存。

class MyPromise {
   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 MyPromise((resolve) => {
          self.callback = function(){
              let data =  fn(self.value)   
              resolve(data)
           }
       })
   }    
}

new MyPromise(executor).map(add1).map(add1)

同時調用同一個Promise函子

Promise除了能鏈式調用,還能同時調用,比如:

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

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

class MyPromise {
   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的奇幻漂流

標准PromiseA+規范實現:這一次,徹底弄懂 Promise 原理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM