從狀態機的角度加深理解並實現Promise

這篇文章內容主要來自一篇stack Overflow高票答案javascript

聲明:此Promise的實現僅僅是爲了加深本人對其的理解,和A+規範有些出入,可是的確是目前看過全部promise代碼中最漂亮,思路比較清晰的一個。
文章不會特地幫助讀者複習Promise基本操做。java

狀態機

Promise其實本質上就是一個狀態機,因此首先咱們描述一個靜態的狀態機,就像下邊這樣git

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // 存儲的狀態是上邊的三個:執行中,已完成,已拒絕
  var state = PENDING;

  // 存儲異步結果或者異步錯誤消息
  var value = null;

  // 負責處理中途加入的處理函數
  var handlers = [];
}

狀態改變

完成了基本的狀態機定義,接下來的問題就是完成「狀態改變」這個動做的實現:github

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // 存儲三個狀態
  var state = PENDING;

  // 一旦出現狀態的改變,異步結果就會被存到這個地方
  var value = null;

  // 存儲成功或者失敗的handler
  var handlers = [];

//狀態轉移到成功
  function fulfill(result) {
    state = FULFILLED;
    value = result;
  }
//狀態轉移到失敗
  function reject(error) {
    state = REJECTED;
    value = error;
  }
}

到目前爲止,咱們給出了兩個很純粹的變化動做,在開發的過程當中這兩個動做會很很差用,因此咱們在這兩個動做的基礎上構建一個高層次的動做(其實就是加點判斷而後封裝一層),就像下邊這兒,名字就叫作resolve,可是注意和咱們正常使用promise調用的那個resolve並不同,不要搞混:promise

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {

  var state = PENDING;
  var value = null;
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED;
    value = result;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }
//這裏暫時缺乏兩個重要函數getThen和doResolve這兩個函數,稍後會說道
  function resolve(result) {
    try {
      var then = getThen(result);
      //判斷then是否是一個Promise對象
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }
}

是的,咱們的用到了兩個輔助函數getThen和doResolve,如今給出實現:異步

/**
 * 這裏會判斷value的類型,咱們只要promise.then這個函數,其餘的通通返回null
 *
 * @param {Promise|Any} value
 * @return {Function|Null}
 */
function getThen(value) {
  var t = typeof value;
  if (value && (t === 'object' || t === 'function')) {
    var then = value.then;
    if (typeof then === 'function') {
      return then;
    }
  }
  return null;
}

/**
 * 這個函數的主要做用就是串主邏輯,完成「變化狀態」這個動做
 *
 * @param {Function} fn A resolver function that may not be trusted
 * @param {Function} onFulfilled
 * @param {Function} onRejected
 */
function doResolve(fn, onFulfilled, onRejected) {
//done的做用是讓onFulfilled或者onRejected僅僅被調用一次,狀態機狀態一旦改變無法回頭
  var done = false;
  try {
//在咱們正常使用Promise的時候調的resolve,其實用的就是這裏fn注入函數而後調用
    fn(function (value) {
      if (done) return
      done = true
      **onFulfilled(value)**
    }, function (reason) {
      if (done) return
      done = true
      onRejected(reason)
    })
  } catch (ex) {
    if (done) return
    done = true
    onRejected(ex)
  }
}

構建

好了,一個完整的狀態機已經完成,咱們完成了一個基本的狀態變化邏輯,接下來要作的就是一步一步的朝promise標準進發,這個promise缺乏什麼呢,暫時缺的就是初始的動做啦(new promise(func)對象一旦被初始化內部代碼當即執行),因此咱們加上初始動做的開啓async

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise(fn) {

  var state = PENDING;
  var value = null;
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED;
    value = result;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }
//開啓任務的執行,因此我說doResolve其實才是「主線任務」的引子,而fn其實就是你寫的代碼
  doResolve(fn, resolve, reject);
}

聯動

咱們實現了狀態機,可是目前的問題是咱們只能眼睜睜的看着代碼的流動直到一個Promise結束爲止,即無法添加也無法獲取結果,這就有很大的侷限性了,因此咱們要使用then方法來串聯Promise,用done方法來完成結果的收集,首先實現done方法,由於then其實說白了就是收集上邊的結果--完成本身的邏輯--把結果傳遞給下一個Promise,作的事情是done的超集。函數

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise(fn) {

  var state = PENDING;
  var value = null;
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED;
    value = result;
    //專門封裝一個handle函數處理後續邏輯,在下面有this.handle(handler)方法
    handlers.forEach(handle);
    //在狀態變成已處理而且以前加入的handler都被處理完畢的狀況下再加入handler就會報錯而且沒有卵用
    handlers = null;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
    handlers.forEach(handle);
    handlers = null;
  }

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }

  function handle(handler) {
    if (state === PENDING) {
      handlers.push(handler);
    } else {
      if (state === FULFILLED &&
        typeof handler.onFulfilled === 'function') {
        handler.onFulfilled(value);
      }
      if (state === REJECTED &&
        typeof handler.onRejected === 'function') {
        handler.onRejected(value);
      }
    }
  }

//注意看下面done方法的實現,裏邊只有一個異步方法,換句話說就是會當即返回不會產生阻塞,咱們以後會在then當中調用done方法,這裏的onFulfilled, onRejected就是用戶寫的處理函數,promise異步的特性就是這樣來的。
  this.done = function (onFulfilled, onRejected) {
    // ensure we are always asynchronous
    setTimeout(function () {
      handle({
        onFulfilled: onFulfilled,
        onRejected: onRejected
      });
    }, 0);
  }

  doResolve(fn, resolve, reject);
}

最後,咱們來實現Promise.then,完成狀態機的串聯:性能

//這段代碼有點繞,主要須要完成的工做其實就是,判斷上一個Promise是否完成,而後執行用戶的then裏邊的回調代碼,並最終返回一個新的Promise,而後依次循環。。。
this.then = function (onFulfilled, onRejected) {
//開啓then以後就會返回一個新的promise,可是這個時候咱們還可能有上一個Promise的任務沒有完成,因此先把上邊一個promise對象的this指向保存下來
  var self = this;
//返回一個新包裝Promise,這和咱們普通的在外邊寫new Promise是一個道理
  return new Promise(function (resolve, reject) {
//done的代碼一樣是當即返回,而後異步執行的
    return self.done(function (result) {
      if (typeof onFulfilled === 'function') {
        try {
          return resolve(onFulfilled(result));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return resolve(result);
      }
    }, function (error) {
      if (typeof onRejected === 'function') {
        try {
          return resolve(onRejected(error));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return reject(error);
      }
    });
  });
}

Over
更多參考請看下面:
簡單的實現Promsie
高性能實現Promise,以及專門的wikithis

相關文章
相關標籤/搜索