談談你對Promise的理解

背景

Promise是爲了解決Javascript回調嵌套過多致使回調地獄(callbackhell)而產生的。目前已被歸入了es2015規範,主流瀏覽器都支持Promise。爲了在工做中更好的運用Promise,咱們須要理解Promise規範與內部實現機制,下面咱們來手動實現一個Promise。javascript

Promise/A+規範

在寫代碼以前讓咱們先了解下 Promise/A+規範。
一個promise有三種狀態:java

  • pending:表示初始狀態,能夠轉移到 fullfilled 或者 rejected 狀態
  • fulfilled:表示操做成功,不可轉移狀態
  • rejected:表示操做失敗,不可轉移狀態
  • 必須有一個 then 異步執行方法,then 接受兩個參數且必須返回一個promise:

借用這張來自MDN的流程圖咱們能夠清晰的看到 Promise 狀態的流轉過程。promise

簡單版

下面咱們來實現一個簡單版的 Promise:瀏覽器

function Promise1(executor){
  let self = this;
	self.status = 'pending';
  self.value = undefined;
  self.reason = undefined;
  
  function resolve(value) {
  	if(self.status==='pending'){
    	self.status = 'fullfilled';
      self.value = value;
    }
  }
  function reject(reason) {
  	if(self.status==='pending') {
    	self.status = 'rejected';
      self.reason = reason;
    }
  }
  
  try{
  	executor(resolve,reject);
  }catch(e) {
  	reject(e);
  }
}

Promise1.prototype.then = function(onFullfilled,onRejected) {
	if(this.status==='fullfilled') {
  	onFullfilled(this.value);
  }
  if(this.status==='rejected') {
  	onRejected(this.reason);
  }
}
//測試
let p= new Promise1(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})
//輸出1
複製代碼

支持異步

如今,咱們實現了最簡單的 Promise。以上版本的Promise是存在不少問題的。爲何呢?最大的問題是它不支持異步,然而在現實中,Promise絕大多數使用場景都是異步。讓咱們來爲 Promise 加入異步功能。異步

const PENDING = 'pending';
const FULFILLED = 'fullfilled';
const REJECTED = 'rejected';
function Promise1(executor){
  let self = this;
	self.status = PENDING;
  self.value = undefined;
  self.reason = undefined;
  self.fullfilledCallbacks = [];
  self.rejectedCallbacks = [];

  function resolve(value) {
    if(value instanceof Promise) {
    	value.then(resolve,reject);
    }
  	setTimeout(function(){
    	if(self.status===PENDING){
        self.status = FULFILLED;
        self.value = value;
        self.fullfilledCallbacks.forEach(function(cb){
        	cb(self.value)
        })
      }
    })
  }
  function reject(reason) {
  	setTimeout(function(){
    	if(self.status===PENDING) {
        self.status = REJECTED;
        self.reason = reason;
        self.rejectedCallbacks.forEach(function(cb){
        	cb(self.reason);
        })
      }
    })
  }

  try{
  	executor(resolve,reject);
  }catch(e) {
  	reject(e);
  }
}

Promise1.prototype.then = function(onFulfilled,onRejected) {
  let self = this;
  return new Promise1(function(resolve,reject){
  		function success(value) {
        let _value = (typeof onFulfilled === 'function' && onFulfilled(value)) || value;
        resolve(_value)
      }
      function error(reason) {
        let _reason = (typeof onRejected === 'function' && onRejected(reason)) || reason;
        reject(_reason);
      }
    	if(self.status==PENDING) {
      	self.fullfilledCallbacks.push(success);
        self.rejectedCallbacks.push(error);
      } else if(self.status==FULLFILLED){
      	success.call(this,this.value)
      } else if(self.status==REJECTED) {
      	error.call(this,this.reason);
      }
  })
}

複製代碼

以上代碼中,咱們作了以下更改:函數

  1. 將 Promise 三個狀態定義爲常量,方便維護
  2. 對於 Promise resolve和reject 函數執行加入異步處理
  3. 在Promise.then中返回新的Promise對象,使Promise能夠支持鏈式調用

錯誤處理以及靜態方法

下面讓咱們來爲Promise 添加錯誤處理以及靜態方法:學習

//錯誤處理
Promise1.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
}

//返回fullfilled Promise對象
Promise1.resolve = function (value) {
    return new Promise1(resolve => {
        resolve(value);
    });
}
//返回 rejected Promise 對象
Promise1.reject = function (reason) {
    return new Promise1((resolve, reject) => {
        reject(reason);
    });
}
//Promise.all方法
Promise1.all = function(promises) {
    function gen(length, resolve) {
        let count = 0;
        let values = [];
        return function(i, value) {
            values[i] = value;
            if (++count === length) {
                resolve(values);
            }
        }
    }
    return new Promise1((resolve, reject) => {
        let done = gen(promises.length, resolve);
        promises.forEach((promise, index) => {
            promise.then((value) => {
                done(index, value)
            }, reject)
        })
    })
}
//Promise.race方法
Promise1.race = function(promises) {
    return new Promise1((resolve, reject) => {
        promises.forEach((promise, index) => {
           promise.then(resolve, reject);
        });
    });
}

複製代碼

這裏有個問題,就是在當咱們console.log(Promise1.resolve('a'))的時候,我發現打印出來的狀態居然是 pending狀態,我猜測緣由是應該是resolve中函數異步執行,在當咱們console的時候setTimeout中代碼未執行,因此我給出的解決方法是將狀態變化與賦值移到setTimeout外面,這樣就不會產生剛纔的問題了,更改後代碼長這樣:測試

function resolve(value) {
    if(value instanceof Promise) {
    	value.then(resolve,reject);
    }
  	self.status = FULFILLED;
  	self.value = value;
  	setTimeout(function(){
    	if(self.status===PENDING){
        self.fullfilledCallbacks.forEach(function(cb){
        	cb(self.value)
        })
      }
    })
  }

function reject(reason) {
    self.status = REJECTED;
    self.reason = reason;
  	setTimeout(function(){
    	if(self.status===PENDING) {
        self.rejectedCallbacks.forEach(function(cb){
        	cb(self.reason);
        })
      }
    })
  }
複製代碼

總結

通過以上實踐,咱們成功的手寫了一個功能完備的 Promise。這裏給個人最大啓發是若是咱們想學習一個東西,必須深刻到它的底層,瞭解它的運行原理與具體實現方法,而且能夠造一個簡單的輪子,這樣纔算咱們掌握了該知識點。從前的我對於這一點沒有關注的太多,致使在用某個知識點時只是掌握的它的表層用法,在高級一點的使用場景時徹底不會運用。之後我會更加註重源碼方面的學習,彌補我這方面的不足。ui

相關文章
相關標籤/搜索