Promise 源碼:實現一個簡單的 Promise

前言

Promise 是 ES6 新增的一個內置對象, 它是用來避免回調地獄的一種解決方案。git

從之前一直嵌套傳回調函數,到使用 Promise 來鏈式異步回調。Promise 到底是怎麼實現,從而達到回調函數「扁平化」?github

接下來就來一步步實現一個簡單的 Promise。開始發車了...數組

執行步驟

先來看一個使用 Promise 的簡單例子:promise

var p = new Promise(function a (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
})

p.then(function b (val) {
  console.log(val);
});
複製代碼

代碼執行,它會先執行函數 a,打印出 1。定時器 1 秒後執行 resolve,緊接着執行了函數 b。異步

更詳細的步驟是這樣子的:函數

  1. new Promise,執行 Promise 構造函數;
  2. 構造函數裏,執行 a 函數;
  3. 執行 then 函數;
  4. 1 秒後,執行 resolve 函數;
  5. 執行 b 函數。

這裏的一個思路就是,在 then 函數執行時用一個屬性保存函數 b,而後在 resolve 執行時再將其執行。測試

開始封裝

這裏定義一個 MyPromise,它有 then 函數,還有一個 callback 屬性用來保存上面的「b 函數」。ui

function MyPromise (fn) {
  var _this = this;
  
  // 用來保存 then 傳入的回調函數
  this.callback = undefined;
  
  function resolve (val) {
    _this.callback && _this.callback(val);
  }
  
  fn(resolve);
}

MyPromise.prototype.then = function (cb) {
  this.callback = cb;
};
複製代碼

測試使用:this

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

p.then(function (val) {
  console.log(val);
});
複製代碼

代碼執行時會立刻打印出 1,1秒後打印出 2。毛問題。spa

多個 resolve 調用處理

上面已經實現了一個簡單的 Promise,固然還有不少種狀況須要考慮。

好比會有這麼一種狀況,調用了多個 resolve 函數:

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
    resolve(3);
    resolve(4);
  }, 1000);
});
複製代碼

原生的 Promise 在調用了第一個 resolve 以後,後面的 resolve 都無效化,即後面的 resolve 都是沒用的代碼。

這裏的處理方式是,給 MyPromise 再添加一個屬性 isResolved,用來記錄是否調用過 resolve 函數。若是調用過,用它標識一下。再有 resolve 的調用,則用它判斷返回。

function MyPromise (fn) {
  var _this = this;

  this.callback = undefined;
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;
    _this.callback && _this.callback(val);
  }
  
  fn(resolve);
}
複製代碼

多個 then 處理

繼續走,除了能夠調用多個 resolve 函數,一樣咱們能夠調用多個 then 函數。

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

p.then(function (val) {
  console.log(val);
});

p.then(function (val) {
 console.log(val);
});
複製代碼

與 resolve 不一樣,這裏的每個傳給 then 的回調函數都會在 1 秒後執行,即 then 函數都有效。代碼執行,先打印出 1。1 秒後,打印兩個 2。

因此 MyPromise 的 callback 屬性需改爲數組的格式,保存着每個 then 的回調函數。

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;
    
    if (_this.callback.length > 0) {
      _this.callback.forEach(function (func) {
        func && func(val);
      });
    }
  }
  
  fn(resolve);
}

MyPromise.prototype.then = function (cb) {
  this.callback.push(cb);
};
複製代碼

在屢次調用 then 時,MyPromise 經過屬性 callback 來保存多個回調函數。在 resolve 執行後,再去遍歷 callback,將它保存的回調函數逐個執行。

支持 then 鏈式調用

Promise 相對於回調地獄而言,它的優點在於能夠進行 then 的鏈式調用,從而將回調函數「扁平化」。

好比我有一個異步操做須要在另外一個異步操做後才能執行,只須要繼續調用 then 就可以下一步回調。

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

p.then(function (val) {
  console.log(val);
}).then(function (val) {
  console.log(val);
});
複製代碼

既然要可以 then 鏈式調用,那我在執行完 then 函數後返回 this 不就能夠啦。但這不就跟剛剛的代碼同樣了嗎?

p.then(function (val) {
  console.log(val);
});

p.then(function (val) {
  console.log(val);
});
複製代碼

這樣就會在調用 resolve 函數以後同時執行,而不是執行完第一個 then 後,再執行第二個。因此直接返回 this 的方案是不行的。

咱們再想,還有什麼能夠返回的,而且帶有 then 函數的。答案就是 new 一個新的 MyPromise 並返回。咱們需重寫一個 then 函數的實現。

MyPromise.prototype.then = function (cb) {
  var _this = this;

  return new MyPromise(function (resolve) {
    _this.callback.push({
      cb: cb,
      resolve: resolve
    });
  });
};
複製代碼

這裏 callback 從新改寫了一下,保存的是 then 的回調函數,和新 new 的 MyPromise 的 resolve 函數。保存的 resolve 函數先不執行,由於咱們知道,它一旦執行了,就會觸發傳入 then 的回調函數的執行。

同時,MyPromise 構造函數裏的 resolve 也須要調整一下:

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;

    if (_this.callback.length > 0) {
       _this.callback.forEach(function (item) {
       var res;
       var cb = item.cb;
       var resolve = item.resolve;
       
       cb && (res = cb(val));
       resolve && resolve(res);
     });
    }
  }
  
  fn(resolve);
}
複製代碼

在執行第一個 then 的回調函數時,將其執行完返回的值,做爲保存的 resolve 的參數傳入。

p.then(function (val) {
  console.log(val);
  return val + 1;
}).then(function(val) {
  console.log(val);
});
複製代碼

這樣子,就可以鏈式的 then 調用。先別急,咱們實現的只是 then 的同步鏈式調用,而咱們最終要的是異步的鏈式調用。

咱們須要這樣子的:

p.then(function (val) {
  console.log(val);
  
  return new MyPromise(function (resolve) {
    setTimeout(function () {
      resolve(val + 1);
    }, 1000);  
  });
}).then(function(val) {
  console.log(val);
});
複製代碼

先打印出 1,1秒後打印出第一個 then 裏的 2,再過多一秒,打印出第二個 then 的 3。

因此,咱們須要在取出原來保存的 cb 返回的值進行判斷。若是該值是一個 MyPromise 對象,則調用它的 then,不然跟原來同樣調用。

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;

    if (_this.callback.length > 0) {
      _this.callback.forEach(function (item) {
        var res;
        var cb = item.cb;
        var resolve = item.resolve;
        
        cb && (res = cb(val));
        if (typeof res === 'object' && res.then) {
          res.then(resolve);
        } else {
          resolve && resolve(res);
        }
      });
    }
  }
  
  fn(resolve);
}
複製代碼

最後

在咱們實現的 MyPromise 裏,有兩個屬性,分別是 isResolvedcallback。isResolved 是一個標識,用來防止屢次調用 resolve。callback 是一個數組,用來保存回調函數。

MyPromise 還有一個 then 函數,用來處理異步後的回調,可以鏈式異步調用。

這樣子就實現了一個簡單的 Promise,完整的代碼戳 這裏

相關文章
相關標籤/搜索