從零開始實現一個本身的Promise庫

剛開始寫前端的時候,處理異步請求常常用callback,簡單又順手。後來寫着寫着就拋棄了callback,開始用promise來處理異步問題。promise寫起來確實更加優美,但因爲缺少對它內部結構的深入認識,每次在遇到一些複雜的狀況時,promise用起來老是不那麼駕輕就熟,debug也得搞半天。前端

因此,這篇文章我會帶你們從零開始,手寫一個基本能用的promise。跟着我寫下來之後,你會對promise是什麼以及它的內部結構有一個清楚的認知,將來在複雜場景下使用promise也能如魚得水。segmentfault

什麼是Promise

回到正文,什麼是Promise?說白了,promise就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。promise

首先,ES6規定Promise對象是一個構造函數,用來生成Promise實例。而後,這個構造函數接受一個函數(executor)做爲參數,該函數的兩個參數分別是resolve和reject。最後,Promise實例生成之後,能夠用then方法分別指定resolved狀態和rejected狀態的回調函數(onFulfilled和onRejected)。緩存

具體的使用方法,用代碼表現是這樣:併發

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操做成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

理解了這個後,咱們就能夠大膽的開始構造咱們本身的promise了,咱們給它取個名字:CutePromise異步

實現一個Promise:CutePromise

咱們直接用ES6的class來建立咱們的CutePromise,對ES6語法還不熟悉的,能夠先讀一下個人另外兩篇介紹ES6核心語法的文章後再回來。30分鐘掌握ES6/ES2015核心內容(上)30分鐘掌握ES6/ES2015核心內容(下)函數

class CutePromise {

  // executor是咱們實例化CutePromise時傳入的參數函數,它接受兩個參數,分別是resolve和reject。
  // resolve和reject咱們將會定義在constructor當中,供executor在執行的時候調用
  constructor(executor) {
    const resolve = () => {}
    const reject = () => {}
    
    executor(resolve, reject)
  }

  // 爲實例提供一個then的方法,接收兩個參數函數,
  // 第一個參數函數必傳,它會在promise已成功(fulfilled)之後被調用
  // 第二個參數非必傳,它會在promise已失敗(rejected)之後被調用
  then(onFulfilled, onRejected) {}

}

建立了咱們的CutePromise後,咱們再來搞清楚一個關鍵點:Promise 對象的狀態。oop

Promise 對象經過自身的狀態,來控制異步操做。一個Promise 實例具備三種狀態:測試

  1. 異步操做未完成(pending)
  2. 異步操做成功(fulfilled)
  3. 異步操做失敗(rejected)

上面三種狀態裏面,fulfilled和rejected合在一塊兒稱爲resolved(已定型)。狀態的切換隻有兩條路徑:第一種是從pending=>fulfilled,另外一種是從pending=>rejected,狀態一旦切換就不能再改變。this

如今咱們來爲CutePromise添加狀態,大概流程就是:
首先,實例化初始過程當中,咱們先將狀態設爲PENDING,而後當executor執行resolve的時候,將狀態更改成FULFILLED,當executor執行reject的時候將狀態更改成REJECTED。同時更新實例的value。

constructor(executor) {
    ...
    this.state = 'PENDING';
    ...
    const resolve = (result) => {
      this.state = 'FULFILLED';
      this.value = result;
    }
    const reject = (error) => {
      this.state = 'REJECTED';
      this.value = error;
    }
    ...
}

再來看下咱們的then函數。then函數的兩個參數,onFulfilled表示當promise異步操做成功時調用的函數,onRejected表示當promise異步操做失敗時調用的函數。假如咱們調用then的時候,promise已經執行完成了(當任務是個同步任務時),咱們能夠直接根據實例的狀態來執行相應的函數。假如promise的狀態仍是PENDING, 那咱們就將onFulfilled和onRejected直接存儲到chained這個變量當中,等promise執行完再調用。

constructor(executor) {
    ...
    this.state = 'PENDING';
    
    // chained用來儲存promise執行完成之後,須要被依次調用的一系列函數
    this.chained = [];
    
    const resolve = (result) => {
      this.state = 'FULFILLED';
      this.value = result;
      
      // promise已經執行成功了,能夠依次調用.then()函數裏的onFulfilled函數了
      for (const { onFulfilled } of this.chained) {
          onFulfilled(res);
      }
    }

    ...
}
then(onFulfilled, onRejected) {
  if (this.state === 'FULFILLED') {
    onFulfilled(this.value);
  } else if (this.state === 'REJECTED') {
    onRejected(this.value);
  } else {
    this.$chained.push({ onFulfilled, onRejected });
  }
}

這樣咱們就完成了一個CutePromise的建立,下面是完整代碼,你們能夠複製代碼到控制檯測試一下:

class CutePromise {

  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new Error('Executor must be a function');
    }

    this.state = 'PENDING';
    this.chained = [];

    const resolve = res => {
      if (this.state !== 'PENDING') {
        return;
      }

      this.state = 'FULFILLED';
      this.internalValue = res;

      for (const { onFulfilled } of this.chained) {
        onFulfilled(res);
      }
    };
    const reject = err => {
      if (this.state !== 'PENDING') {
        return;
      }
      this.state = 'REJECTED';
      this.internalValue = err;
      for (const { onRejected } of this.chained) {
        onRejected(err);
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
    
  then(onFulfilled, onRejected) {
    if (this.state === 'FULFILLED') {
      onFulfilled(this.internalValue);
    } else if (this.$state === 'REJECTED') {
      onRejected(this.internalValue);
    } else {
      this.chained.push({ onFulfilled, onRejected });
    }
  }
}

提供一下測試代碼:

let p = new CutePromise(resolve => {
  setTimeout(() => resolve('Hello'), 100);
});

p.then(res => console.log(res));

p = new CutePromise((resolve, reject) => {
  setTimeout(() => reject(new Error('woops')), 100);
});

p.then(() => {}, err => console.log('Async error:', err.stack));

p = new CutePromise(() => { throw new Error('woops'); });

p.then(() => {}, err => console.log('Sync error:', err.stack));

實現鏈式調用

實現鏈式調用其實很簡單,只須要在咱們定義的then()方法裏返回一個新的CutePromise便可。

then(onFulfilled, onRejected) {
    return new CutePromise((resolve, reject) => {

      const _onFulfilled = res => {
        try {
          //注意這裏resolve有可能要處理的是一個promise
          resolve(onFulfilled(res));
        } catch (err) {
          reject(err);
        }
      };
      const _onRejected = err => {
        try {
          reject(onRejected(err));
        } catch (_err) {
          reject(_err);
        }
      };
      if (this.state === 'FULFILLED') {
        _onFulfilled(this.internalValue);
      } else if (this.state === 'REJECTED') {
        _onRejected(this.internalValue);
      } else {
        this.chained.push({ onFulfilled: _onFulfilled, onRejected: _onRejected });
      }
    });
  }

不過,咱們還須要解決一個問題:假如then函數的第一個參數onfulfilled()自己返回的也是一個promise怎麼辦?好比下面這種使用方式,實際上是最真實項目場景中最多見:

p = new CutePromise(resolve => {
  setTimeout(() => resolve('World'), 100);
});

p.
  then(res => new CutePromise(resolve => resolve(`Hello, ${res}`))).
  then(res => console.log(res));

因此咱們須要讓咱們的resolve方法可以處理promise:

const resolve = res => {
      if (this.state !== 'PENDING') {
        return;
      }
      
      // 假如說res這個對象有then的方法,咱們就認爲res是一個promise
      if (res != null && typeof res.then === 'function') {
        return res.then(resolve, reject);
      }
    ...
}

三道思考題

  1. promise array的鏈式調用?
  2. promise怎麼作併發控制?
  3. promise怎麼作異步緩存?

以上三道思考題其實跟你用不用promise並無多大關係,可是若是你不深入理解promise想要解決這三個問題還真不是那麼輕鬆的。

參考:https://brunoscopelliti.com/l...

相關文章
相關標籤/搜索