從零開始完成一個本身的Promise庫

剛最先寫前端的時刻,處置懲罰異步要求經經常運用callback,簡樸又隨手。厥後寫着寫着就揚棄了callback,最先用promise來處置懲罰異步題目。promise寫起來確切越發幽美,但由於缺少對它內部結構的深入熟習,每次在碰到一些龐雜的狀況時,promise用起來老是不那末隨心所欲,debug也得搞半天。

所以,這篇文章我會帶人人從零最先,手寫一個基礎能用的promise。隨着我寫下來今後,你會對promise是什麼以及它的內部結構有一個清晰的認知,將來在龐雜場景下運用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 對象的狀況。

Promise 對象經由過程自身的狀況,來掌握異步操縱。一個Promise 實例具有三種狀況:

  1. 異步操縱未完成(pending)
  2. 異步操縱勝利(fulfilled)
  3. 異步操縱失利(rejected)

上面三種狀況內里,fulfilled和rejected合在一起稱為resolved(已定型)。狀況的切換隻要兩條途徑:第一種是從pending=>fulfilled,另一種是從pending=>rejected,狀況一旦切換就不能再轉變。

如今我們來為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…

    原文作者:zach5078
    原文地址: https://segmentfault.com/a/1190000014440641
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞