Promise是如何實現非同步程式設計的?

wangmeijian發表於2020-12-29

Promise標準

不能免俗地貼個Promise標準連結Promises/A+。ES6的Promise有很多方法,包括Promise.all()/Promise.resolve()/Promise.reject()等,但其實這些都是Promises/A+規範之外的,Promises/A+規範只定義了一個Promise.then()方法,這是Promise的核心。

基本結構

new Promise((resolve, reject) => {
  let a = 0;
  if (a > 1) {
    resolve(a);
  } else {
    reject(a);
  }
}).then(res => {
  console.log(res);
}, err => {
  console.log(err);
})

Promise接收一個函式作為引數,我們稱之為executor,該函式有兩個引數resolve和reject,這兩個引數也都是函式,並且,它們定義在Promise內部。

那麼我們定義一個class並定義一個_isFunction方法,用來校驗建構函式的引數必須是函式。再定義resolve和reject這兩個方法。

class MyPromise{
  constructor(executor){
    if(!this._isFunction(executor)){
      throw new Error(`Promise resolver ${executor} is not a function`);
    }
  }
  _isFunction(val){
    return Object.prototype.toString.call(val) === '[object Function]';
  }
  _resolve(){
    
  }
  _reject(){
    
  }
}

Promise狀態、resolve、reject

Promise有三種狀態,分別是pending(等待中)、fulfilled(成功)、rejected(失敗)。狀態改變只能從pending => fulfilled,或者pending => rejected。

resolve的作用,就是將Promise的狀態從pending改為fulfilled,它接收一個引數作為Promise執行成功的值,這個值會傳給then的第一個回撥函式。reject的作用是將Promise的狀態從pending改為rejected,它也接收一個引數作為Promise執行失敗的值,這個值會傳給then的第二個回撥函式。

那麼我們定義好狀態_status、_resolve、_reject,再定義兩個陣列_handleFulfilled、_handleRejected,分別存放then的成功和失敗回撥集合。當使用者呼叫resolve或reject方法後,開始非同步呼叫_handleFulfilled或_handleRejected陣列中的回撥。

class MyPromise {
  constructor(executor) {
    if (!this._isFunction(executor)) {
      throw new Error(`${executor} is not a function`);
    }
    this._status = "pending";
    this._value = undefined;
    this._handleFulfilled = [];
    this._handleRejected = [];
    // 很多文章在這裡給executor加了try catch,實際上原生Promise的executor中的錯誤並沒有捕獲
    executor(this._resolve.bind(this), this._reject.bind(this));
  }
  _isFunction(val) {
    return Object.prototype.toString.call(val) === "[object Function]";
  }
  _resolve(value) {
    if(this._status === 'pending'){
      this._status = "fulfilled";
      this._value = value;
      let cb;
      // 非同步按順序呼叫並清空回撥
      setTimeout(() => {
        while(cb = this._handleFulfilled.shift()){
          cb(value);
        }
      }, 0)
    }
  }
  _reject(value) {
    if(this._status === 'pending'){
      this._status = "rejected";
      this._value = value;
      let cb;
      // 非同步按順序呼叫並清空回撥
      setTimeout(() => {
        while ((cb = this._handleRejected.shift())) {
          cb(value);
        }
      }, 0);
    }
  }
}

Promise.then

Promise.then定義了兩個回撥onFulfilled和onRejected

promise.then(onFulfilled, onRejected)

它們分別在Promise執行成功/失敗時執行,它們都是可選的,Promises/A+規範規定,如果onFulfilled或onRejected不是函式,將被忽略,Promise會繼續執行下一個then的回撥。比如下面的例子會輸出1,.then(2)則被忽略了。

new Promise((resolve, reject) => {
  resolve(1);
})
  .then(2)
  .then((res) => {
    console.log(res);
  });

then可以鏈式呼叫,是因為每個then都會返回一個新的Promise。then執行onFulfilled還是onRejected,取決於Promise的狀態,如果Promise狀態為pending,只會將onFulfilled和onRejected分別push到_handleFulfilled和_handleRejected陣列;如果狀態為fulfilled,會執行對應的onFulfilled;如果狀態是rejected,執行對應的onRejected;

那麼then方法的基本結構如下

then(onFulfilled, onRejected) {
    const self = this;
    const { _value, _status } = this;

  // 如果onFulfilled、onRejected不是函式,強制改為函式,並且該函式直接返回接收到的引數,傳後面的then的回撥函式
  onFulfilled = self._isFunction(onFulfilled) ? onFulfilled : (v) => v;
  onRejected = self._isFunction(onRejected) ? onRejected : (v) => v;

  return new MyPromise((resolve, reject) => {
    switch (_status) {
      case "pending":
        self._handleFulfilled.push(onFulfilled);
        self._handleRejected.push(onRejected);
        break;
      case "fulfilled":
        onFulfilled(_value);
        // todo
        break;
      case "rejected":
        onRejected(_value);
        // todo
        break;
      default:
        throw new Error('Promise resolver Unverified status');
        break;
    }
  });
}

在then鏈式呼叫的情況下,如果前一個then返回的是一個新Promise,後一個then的回撥必須等這個新Promise的狀態改變後才會執行。舉例,下面的程式碼輸出1之後,等待3秒才會輸出2:

new Promise(resolve => {
  resolve()
}).then(() => {
  return new Promise(resolve => {
    console.log(1);
    setTimeout(() => {
      resolve()
    }, 3000)
  })
}).then(() => {
  console.log(2);
})

因此要對then的回撥函式的返回值做個判斷,如果返回值不是Promise,利用resolve直接返回這個值;如果返回值是Promise,就要等這個Promise狀態變化之後再返回,而Promise狀態變化之後一定會呼叫then的回撥函式,利用這個特性,將resolve、reject作為then的回撥函式即可。

then(onFulfilled, onRejected) {
    const self = this;
    const { _value, _status } = this;

    // 如果onFulfilled、onRejected不是函式,強制改為函式,並且該函式直接返回接收到的引數,傳後面的then的回撥函式
    onFulfilled = self._isFunction(onFulfilled) ? onFulfilled : (v) => v;
    onRejected = self._isFunction(onRejected) ? onRejected : (v) => v;

    return new MyPromise((resolve, reject) => {
      const fulfilled = (value) => {
        const res = onFulfilled(value);
        if (res instanceof MyPromise) {
          res.then(resolve, reject);
        } else {
          resolve(res);
        }
      };
      const rejected = (value) => {
        const res = onRejected(value);
        if (res instanceof MyPromise) {
          // 這裡是重點
          res.then(resolve, reject);
        } else {
          reject(res);
        }
      };
      switch (_status) {
        case "pending":
          self._handleFulfilled.push(fulfilled);
          self._handleRejected.push(rejected);
          break;
        case "fulfilled":
          fulfilled(_value);
          break;
        case "rejected":
          rejected(_value);
          break;
        default:
          throw new Error('Promise resolver Unverified status');
          break;
      }
    });
  }

完整程式碼

class MyPromise {
  constructor(executor) {
    if (!this._isFunction(executor)) {
      throw new Error(`${executor} is not a function`);
    }
    this._status = "pending";
    this._value = undefined;
    this._handleFulfilled = [];
    this._handleRejected = [];
    // 很多文章在這裡給executor加了try catch,實際上原生Promise的executor中的錯誤並沒有捕獲
    executor(this._resolve.bind(this), this._reject.bind(this));
  }
  _isFunction(val) {
    return Object.prototype.toString.call(val) === "[object Function]";
  }
  _resolve(value) {
    if (this._status === "pending") {
      this._status = "fulfilled";
      this._value = value;
      let cb;
      // 非同步按順序呼叫並清空回撥
      setTimeout(() => {
        while ((cb = this._handleFulfilled.shift())) {
          cb(value);
        }
      }, 0);
    }
  }
  _reject(value) {
    if (this._status === "pending") {
      this._status = "rejected";
      this._value = value;
      let cb;
      // 非同步按順序呼叫並清空回撥
      setTimeout(() => {
        while ((cb = this._handleRejected.shift())) {
          cb(value);
        }
      }, 0);
    }
  }
  then(onFulfilled, onRejected) {
    const self = this;
    const { _value, _status } = this;

    // 如果onFulfilled、onRejected不是函式,強制改為函式,並且該函式直接返回接收到的引數,傳後面的then的回撥函式
    onFulfilled = self._isFunction(onFulfilled) ? onFulfilled : (v) => v;
    onRejected = self._isFunction(onRejected) ? onRejected : (v) => v;

    return new MyPromise((resolve, reject) => {
      const fulfilled = (value) => {
        const res = onFulfilled(value);
        if (res instanceof MyPromise) {
          res.then(resolve, reject);
        } else {
          resolve(res);
        }
      };
      const rejected = (value) => {
        const res = onRejected(value);
        if (res instanceof MyPromise) {
          // 這裡是重點
          res.then(resolve, reject);
        } else {
          reject(res);
        }
      };
      switch (_status) {
        case "pending":
          self._handleFulfilled.push(fulfilled);
          self._handleRejected.push(rejected);
          break;
        case "fulfilled":
          fulfilled(_value);
          break;
        case "rejected":
          rejected(_value);
          break;
        default:
          throw new Error('Promise resolver Unverified status');
          break;
      }
    });
  }
}

測試一下,先輸出1,3秒後輸出2,說明MyPromise的基本功能沒問題了。

new MyPromise((resolve) => {
  console.log(1);
  setTimeout(() => {
    resolve(2);
  }, 3000)
}).then(res => {
  console.log(res);
})

最後,總結一下,Promise是如何實現非同步程式設計的?

Promise接收一個函式為引數,傳入了兩個內部的方法resolve和reject,然後用then註冊回撥函式,手動呼叫resolve或reject就可以依次執行then的回撥,並且給回撥函式傳值。如果then返回的也是Promise,同樣的,手動呼叫resolve或reject後,才會繼續往下執行。

其實本質上還是回撥函式,只不過寫法變了。

本文GitHub連結:Promise是如何實現非同步程式設計的?

相關文章