來實現一個簡易版的 Promise

YyzclYang發表於2018-12-02

前言

Promise 是 JavaScript 中用於非同步操作非常重要的一個建構函式,那麼它是怎麼實現的呢?

下面用原生 JavaScript 來實現一個簡易版的 Promise,用來實現以下程式碼。

function Promise(???){
    ???
    return ???
}

var promise = new Promise(function(resolve, reject) {
  setTimeout(() => {
    resolve('hello');
  }, 3000);
});

promise.then(
  val => {
    console.log('第一次成功', val);// 輸出 第一次成功 hello
  },
  val => {
    console.log('第一次失敗', val);
  }
);
複製程式碼

實現

用過 Promise 的朋友都知道,Promise 要接受一個函式 fn 並呼叫它,Promise 還應該有一個表示狀態( pending, resolved, rejected )的屬性值 status,一個 value 儲存 then 回撥時傳的值,還有一個 then 方法。其他方法暫時不考慮了。

function Promise(fn) {
  //初始 status 為 peding
  let status = 'pending';
  //儲存 then 回撥值
  let value;
  //儲存 then 的兩個引數
  let onResolvedCallback;
  let onRejectedCallback;

  //設定 fn 的 resolve 函式
  function resolve(data) {
    //將 status 變成 fulfilled
    status = 'fulfilled';
    //儲存回撥函式的值
    value = data;
    toDoThen(onResolvedCallback, onRejectedCallback);
  }

  //設定 fn 的 reject 函式
  function reject(data) {
    //將 status 變成 rejected
    status = 'rejected';
    //儲存回撥函式的值
    value = data;
    toDoThen(onResolvedCallback, onRejectedCallback);
  }

  //設定 resolve 或 reject 時的執行體
  function toDoThen(onFulfill, onReject) {
    //如果是 fulfilled 狀態,表示要執行 then 的第一個函式
    if (status === 'fulfilled') {
      onFulfill && onFulfill(value);
      status = 'pending';
    //如果是 rejected 狀態,表示要執行 then 的第二個函式
    } else if (status === 'rejected') {
      onReject && onReject(value);
      status = 'pending';
    //如果狀態還是 peding,表示 resolve 或 reject 還沒執行,那就先把 then 的兩個函式存起來,等 resolve 或 reject 的時候再呼叫
    } else {
      onResolvedCallback = onFulfill;
      onRejectedCallback = onReject;
    }
  }

  //將 then 得到的函式傳給 toDoThen
  this.then = function(onFulfill, onReject) {
    toDoThen(onFulfill, onReject);
  };

  //執行 fn
  fn(resolve, reject);

  return this;
}
複製程式碼

測試一波

var promise = new Promise(function(resolve, reject) {
  setTimeout(() => {
    resolve('hello');
  }, 3000);
});

promise.then(
  val => {
    console.log('第一次成功', val);
  },
  val => {
    console.log('第一次失敗', val);
  }
);
複製程式碼

執行時會列印出 '第一次成功' 和 'hello'

鏈式 then

如果想要實現以下的程式碼,讓第二個 then 的執行函式是根據第一個 then 返回值的布林值來確定的。

promise
  .then(
    val => {
      console.log('第一次成功', val);
      return 'world';
    },
    val => {
      console.log('第一次失敗', val);
      return false;
    }
  )
  .then(
    val => {
      console.log('第二次成功', val);
    },
    val => {
      console.log('第二次失敗', val);
    }
  );
複製程式碼

這裡如果要實現鏈式 then,那麼 then 返回的也必須是一個 Promise 物件,並根據第一個 then 的返回值來確定下一個 then 呼叫哪個函式。

function Promise(fn) {
  //初始 status 為 peding
  let status = 'pending';
  //儲存 then 回撥值
  let value;
  //儲存 then 的兩個引數
  let onResolvedCallback;
  let onRejectedCallback;

  //設定 fn 的 resolve 函式
  function resolve(data) {
    //將 status 變成 fulfilled
    status = 'fulfilled';
    //儲存回撥函式的值
    value = data;
    toDoThen(onResolvedCallback, onRejectedCallback);
  }

  //設定 fn 的 reject 函式
  function reject(data) {
    //將 status 變成 rejected
    status = 'rejected';
    //儲存回撥函式的值
    value = data;
    toDoThen(onResolvedCallback, onRejectedCallback);
  }

  //設定 resolve 或 reject 時的執行體
  function toDoThen(onFulfill, onReject) {
    //如果是 fulfilled 狀態,表示要執行 then 的第一個函式
    if (status === 'fulfilled') {
      onFulfill && onFulfill(value);
      status = 'pending';
    //如果是 rejected 狀態,表示要執行 then 的第二個函式
    } else if (status === 'rejected') {
      onReject && onReject(value);
      status = 'pending';
    //如果狀態還是 peding,表示 resolve 或 reject 還沒執行,那就先把 then 的兩個函式存起來,等 resolve 或 reject 的時候再呼叫
    } else {
      onResolvedCallback = onFulfill;
      onRejectedCallback = onReject;
    }
  }

  this.then = function(onFulfill, onReject) {
    return new Promise((resolve, reject) => {
      toDoThen(
        val => {
          let result = onFulfill(val);
          //根據第一個 then 的返回值來確定下一個 then 的呼叫
          result ? resolve(result) : reject(result);
        },
        err => {
          let result = onReject(err);
          //根據第一個 then 的返回值來確定下一個 then 的呼叫
          result ? resolve(result) : reject(result);
        }
      );
    });
  };

  //執行 fn
  fn(resolve, reject);

  return this;
}
複製程式碼

來執行一下

var promise = new Promise(function(resolve, reject) {
  setTimeout(() => {
    resolve('hello');
  }, 3000);
});

promise
  .then(
    val => {
      console.log('第一次成功', val);
      return 'world';
    },
    val => {
      console.log('第一次失敗', val);
      return false;
    }
  )
  .then(
    val => {
      console.log('第二次成功', val);
    },
    val => {
      console.log('第二次失敗', val);
    }
  )
複製程式碼

程式會依次輸出 '第一次成功' 'hello' 和 '第二次成功' 'world' 。

到此就實現了一個簡易版的 Promise,當然,還有很多地方細節沒有考慮到,實現地非常粗糙。

相關文章