實現Promise

使用Promise是極好的,它是如此有用以致於我以爲應該好好研究一下Promise,甚至是實現一個簡易的版本。實現以前,咱們先來看看Promise的用途:html

使用Promise

callback hell

Promise的第一個用途是可以很好地解決回調黑洞的問題,假設要實現一個用戶展現的任務,這個任務分爲三步:node

  1. 獲取用戶信息git

  2. 獲取用戶圖像github

  3. 彈窗提示編程

不使用Promise,咱們的實現多是這樣子:數組

getUserInfo(id, function (info) {
  getUserImage(info.img, function () {
    showTip();
  })
})

這裏只是三步,若是有更長串的任務時,咱們就會陷入到回調黑洞之中,爲了解決這個問題,咱們就可使用Promise來處理這一長串任務,使用Promise的版本是這樣子的:promise

// getUserInfo返回promise
getUserInfo(id)
  .then(getUserImage)
  .then(showTip)
  .catch(function (e) {
     console.log(e);
  });

原來向右發展的代碼,開始向下發展,這樣也更適合編程習慣,若是要讓咱們的代碼更加健壯,咱們就須要在每一步來處理錯誤信息,使用promise這後,咱們只須要在最後的catch中作善後處理。瀏覽器

併發

假如咱們要顯示某一個頁的10條記錄,可是咱們只有一個經過id獲取記錄的接口,這樣咱們就須要發送10個請求,而且全部請求都完成以後再將記錄所有添加到頁面之中,Promise在這個場景下使用是特別合適的。併發

代碼多是這樣子:異步

// ids要獲取信息的全部記錄id
// getRecordById獲取記錄的接口,返回promise
Promise.all(ids.map(getRecordById))
  .then(showRecords)
  .catch(function (e) {
     console.log(e);
  });

這就是Promise的一些簡單的用途,固然使人興奮的是Promise已是ES6的標準,並且目前不少瀏覽器已經原生支持Promise了。對於那些沒法使用Promise的瀏覽器,咱們就只能本身去實現了,下面就來看看Promise的簡單實現吧。

實現

warm up

先來盜用一張MDN的圖,先來熱熱身,看看Promise的狀態遷移:

promise

Promise有三種狀態:

  1. pending:初始狀態, 非 fulfilled 或 rejected

  2. fulfilled: 成功的操做

  3. rejected: 失敗的操做

咱們能夠看出新建的Promise是pending狀態,fulfill以後就會執行調用then的回調函數了,假若reject了就會調用catch來進行異常處理了,而且不管是調用then仍是catch都會返回新的promise,這就是爲何promise能夠鏈式調用了。

接着,咱們來研究一下規範是怎麼描述
promise的。這裏只抽取核心部分,邊界問題不考慮。

構造函數:Promise ( executor )

  1. 檢查參數:例如executor是否是函數啊

  2. 初始化:[[State]]=pending[[FulfillReactions]]=[],[[RejectReactions]]=[]

  3. 建立resolve對象:{[[Resolve]]: resolve, [[Reject]]: reject}

  4. 執行executor:executor(resolve, reject)

所以構造函數裏面傳入的excuter是當即被執行的。FulfillReactions存儲着promise執行成功時要作的操做,RejectReactions存儲着promise是要執行的操做。

function Promise(resolver) {
  this._id = counter++;
  this._state = PENDING;
  this._result = undefined;
  this._subscribers = [];

  var promise = this;

  if (noop !== resolver) {
    try {
      resolver(function (value) {
        resolve(promise, value);
      }, function (reason) {
        reject(promise, reason);
      });
    } catch (e) {
      reject(promise, e);
    }
  }
}

FulfillPromise(promise, value)

  1. 檢查[[state]],必須爲pending(不是pending的表示已經解析,不能重複解析)

  2. 賦值:[[Result]]=value[[state]]=fulfilled

  3. 觸發[[FulfillReactions]]的操做

和FulfillPromise聯繫最緊密的就是ResolvePromise了,這裏咱們給出的是ResolvePromise的實現,區別只是多了直接解析Promise。

function resolve(promise, value) {
  // 要resolve的爲promise(then的callback返回的是promise)
  if (typeof value === 'object'
    && promise.constructor === value.constructor) {
    handleOwnThenable(promise, value);
  } 
  // 要resolve的是值
  else {
    if (promise._state !== PENDING) { return; }

    promise._result = value;
    promise._state = FULFILLED;

    asap(publish, promise);
  }
}

function handleOwnThenable(promise, thenable) {
  // 若是返回的promise已經完成
  // 直接用該promise的值resolve父promise
  if (thenable._state === FULFILLED) {
    resolve(promise, thenable._result);
  } else if (thenable._state === REJECTED) {
    reject(promise, thenable._result);
  }
  // 若是返回的promise未完成
  // 要等該promise完成再resolve父promise
  else {
    subscribe(thenable, undefined, function(value) {
      resolve(promise, value);
    }, function(reason) {
      reject(promise, reason);
    });
  }
}

RejectPromise(promise, reason)

  1. 檢查[[state]],必須爲pending(不是pending的表示已經解析,不能重複解析)

  2. 賦值:[[Result]]=reason[[state]]=rejected

  3. 觸發[[RejectReactions]]的操做

觸發[[FulfillReactions]]和觸發[[RejectReactions]]實際就是遍歷數組,執行全部的回調函數。

function reject(promise, reason) {
  if (promise._state !== PENDING) { return; }

  promise._state = REJECTED;
  promise._result = reason;

  asap(publish, promise);
}

Promise.prototype.then(onFullfilled, onRejected)

  1. promise=this

  2. 新建resultCapability三元組,{[[Promise]], [[Resolve]], [[Reject]]}([[Promise]]新建的)

  3. fulfillReaction={[[Capabilities]]: resultCapability, [[Handler]]: onFulfilled}

  4. rejectReaction={[[Capabilities]]: resultCapability, [[Handler]]: onRejected}

  5. 若是[[state]]是pending:fulfillReaction加入[[FulfillReactions]],rejectReaction加入[[RejectReactions]]

  6. 若是[[state]]是fulfilled:fulfillReaction加入執行隊列

  7. 若是[[state]]是rejected:rejectReaction加入執行隊列

  8. 返回resultCapability.[[Promise]]

這裏能夠看出構造函數和then的關係是很緊密的,新建的promise若是是異步操做,那麼狀態就是pending,調用then時會新建子promise,而且將回調操做加入父promise的[[FulfillReactions]]或[[RejectReactions]]的數組裏,這實際就是發佈訂閱模式

他們是這樣的關係:

promise構造函數與then的關係

不管是new promise仍是調用then或catch,都會獲得一個新的promise,這些promise都會訂閱父級promise的完成事件,父級promise完成以後就會執行一系列的回調操做,也就是發佈。

Promise.prototype.catch(onRejected)

  • then的語法糖:then(null, onRejected)

下面就是Promise原型:

Promise.prototype = {
  constructor: Promise,

  then: function (onFulfillment, onRejection) {
    var parent = this;
    var state = parent._state;

    if (state === FULFILLED && !onFulfillment
        || state === REJECTED && !onRejection) {
        return this;
    }

    var child = new Promise(noop);
    var result = parent._result;

    if (state) {
      var callback = arguments[state - 1];
      asap(function () {
        invokeCallback(state, child, callback, result);
      });
    } else {
      subscribe(parent, child, onFulfillment, onRejection);
    }

    return child;
  },

  'catch': function (onRejection) {
    return this.then(null, onRejection);
  }
};

Promise.resolve(value)

  1. 新建promise

  2. 調用ResolvePromise(promise, value)(未列出,會判斷一些狀況而後調用FulfillPromise)

  3. 返回promise

Promise.resolve = function (arg) {
  var child = new Promise(noop);
  resolve(child, arg);
  return child;
};

Promise.reject(value)

  1. 新建promise

  2. 調用RejectPromise(promise, value)

  3. 返回promise

Promise.reject = function (reason) {
  var child = new Promise(noop);
  reject(child, reason);
  return child;
};

Promise.all(iterator)

到這裏咱們已經可以實現基本的promise了,Promise.allPromise.race就不繼續描述了,有興趣的能夠繼續去讀規範,這裏上圖來講明我對這兩個函數的理解:

promise.all

調用promise.all會新建一個對象來存儲全部promise的處理狀態,保存執行的結果,當remain爲0時,就能夠resolve 新建的promise,這樣就能夠繼續日後執行了。

Promise.all = function (promises) {
  var child = new Promise(noop);
  var record = {
    remain: promises.length,
    values: []
  };
  promises.forEach(function (promise, i) {
    if (promise._state === PENDING) {
      subscribe(promise, undefined, onFulfilled(i), onRejected);
    } else if (promise._state === REJECTED) {
      reject(child, promise._result);
      return false;
    } else {
      --record.remain;
      record.values[i] = promise._result;
      if (record.remain == 0) {
        resolve(child, values);
      }
   }
  });
  return child;

  function onFulfilled(i) {
    return function (val) {
      --record.remain;
      record.values[i] = val;
      if (record.remian === 0) {
        resolve(child, record.values);
      }
    }
  }
  
  function onRejected(reason) {
    reject(child, reason);
  }
};

Promise.race(iterator)

promise.race與promise.all相似,不過只要有一個promise完成了,咱們就能夠resolve新建的promise了。

Promise.race = function (promises) {
  var child = new Promise(noop);

  promises.forEach(function (promise, i) {
    if (promise._state === PENDING) {
      subscribe(promise, undefined, onFulfilled, onRejected);
    } else if (promise._state === REJECTED) {
      reject(child, promise._result);
      return false;
    } else {
      resolve(child, promise._result);
      return false;
    }
  });
  return child;

  function onFulfilled(val) {
    resolve(child, val);
  }
        
  function onRejected(reason) {
    reject(child, reason);
  }
};

這就是promise的基本內容了,完整代碼請戳這裏

其餘問題

promises 穿透

若是傳入then裏面的參數不是函數,就會被忽略,這就是promise穿透的緣由,因此永遠往then裏面傳遞函數。答案能夠從then方法裏面調用的一個關鍵函數invokeCallback中找到答案:

function invokeCallback(settled, promise, callback, detail) {
    var hasCallback = (typeof callback === 'function'),
      value, error, succeeded, failed;

    if (hasCallback) {

      try {
        value = callback(detail);
      } catch (e) {
        value = {
          error: e
        };
      }

      if (value && !!value.error) {
        failed = true;
        error = value.error;
        value = null;
      } else {
        succeeded = true;
      }

    }
    // then的參數不是函數
    // 會被忽略,也就是promise穿透
    else {
      value = detail;
      succeeded = true;
    }

    if (promise._state === PENDING) {
      if (hasCallback && succeeded
          || settled === FULFILLED) {
        resolve(promise, value);
      } else if (failed || settled === REJECTED) {
        reject(promise, error);
      }
    }
  }

例如以下例子,結果都是輸出foo:

Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {
  console.log(result);
});

Promise.resolve('foo').then(null).then(function (result) {
  console.log(result);
});

擁抱金字塔

promise可以很好的解決金字塔問題,可是有時候咱們也是須要適當使用金字塔的,例如咱們要同時獲取兩個promise的結果,可是這兩個promise是有關聯的,也就是有順序的,該怎麼辦?

也許解決方案會是這樣,定義一個全局變量,這樣在第二個then裏面就可使用兩個promise的結果了。

var user;
getUserByName('nolan').then(function (result) {
  user = result;
  return getUserAccountById(user.id);
}).then(function (userAccount) {
  // 好了, "user" 和 "userAccount" 都有了
});

可是這不是最好的方案,此時何不拋棄成見,擁抱金字塔:

getUserByName('nolan').then(function (user) {
  return getUserAccountById(user.id).then(function (userAccount) {
    // 好了, "user" 和 "userAccount" 都有了
  });
});

promise是如此強大並且難以理解,可是抓住實質以後其實並無想象的那麼複雜,這也是爲何我要寫下這篇文章。更過關於如何正確使用promise,請看第三篇參考文章,強力推薦。

參考

相關文章
相關標籤/搜索