ES6之promise原理

迪迪同學發表於2019-04-28

promise的作用

Promise 是非同步程式設計的一種解決方案,比傳統的解決方案——回撥函式更強大,避免了層層回撥。它由社群最早提出和實現,ES6將其寫進了語言標準,統一了用法,現在原生提供Promise物件!

Promise使用:

// ... some code
const promise = new Promise(function(resolve, reject) {
  if (/* 非同步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
複製程式碼
  • 我們可以看到,promise需要使用new操作符來生成例項,因此Promise是一個建構函式。
  • 這個promise初始化的時候需要傳入一個函式做為引數,並且這個函式的兩個引數分別為:resolve和reject。
  • resolve和reject是兩個函式,由 JavaScript 引擎提供,不用自己部署。
  • resolve函式的作用:將Promise物件狀態從pending 變為 fulfilled
  • reject 函式的作用:將Promise物件狀態從pending 變為 reject

現在讓我們看一下promise的原理

Promise 是一個狀態工具,他有三個狀態:pending, fulfilled, reject,因此我們先定義三個狀態。

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value or error once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers attached by calling .then or .done
  var handlers = [];
}
複製程式碼

Promise 在執行完非同步操作後,會轉變狀態,所以程式碼變為這樣。

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED; // 狀態改變了
    value = result;
  }

  function reject(error) {
    state = REJECTED; // 狀態改變了
    value = error;
  }
}
複製程式碼

上面fulfill是一個比較low level的轉變狀態方法,但是有一個higher-level的轉變狀態方法:resolve

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED;
    value = result;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }
}
複製程式碼

可以看到resolve先呼叫了方法getThen(),這個方法是判斷result是否是promise,如果是的話,呼叫這個promise的then方法(使用doResolve()方法),如果不是的話,呼叫fulfill()方法。

/**
 * Check if a value is a Promise and, if it is,
 * return the `then` method of that promise.
 *
 * @param {Promise|Any} value
 * @return {Function|Null}
 */
function getThen(value) {
  var t = typeof value;
  if (value && (t === 'object' || t === 'function')) {
    var then = value.then;
    if (typeof then === 'function') {
      return then;
    }
  }
  return null;
}

/**
 * Take a potentially misbehaving resolver function and make sure
 * onFulfilled and onRejected are only called once.
 *
 * Makes no guarantees about asynchrony.
 *
 * @param {Function} fn A resolver function that may not be trusted
 * @param {Function} onFulfilled
 * @param {Function} onRejected
 */
function doResolve(fn, onFulfilled, onRejected) {
  var done = false;
  try {
    fn(function (value) {
      if (done) return
      done = true
      onFulfilled(value)
    }, function (reason) {
      if (done) return
      done = true
      onRejected(reason)
    })
  } catch (ex) {
    if (done) return
    done = true
    onRejected(ex)
  }
}
複製程式碼

這裡可以看到,什麼情況會reject:catch到的錯誤會reject, 到現在為止,我們完成了內部狀態機,可是介紹resolving這個promise的方法:

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise(fn) {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED;
    value = result;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }

  doResolve(fn, resolve, reject);
}
複製程式碼

只新增了最後一句程式碼: 我們re-use了doResolve()方法!

現在我們已經完成了狀態機,可是我們仍然無法監聽變化,我們的最終目標是實現.then(), 但是.done()這個更簡單,因此,讓我們先實現.done()吧! 所以我們的目標是實現promise.done(onFulfilled, onRejected), 他有以下特點

  • 只呼叫 onFulfilled or onRejected 中的一個
  • 只呼叫一次
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise(fn) {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED;
    value = result;
    handlers.forEach(handle);
    handlers = null;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
    handlers.forEach(handle);
    handlers = null;
  }

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }

  function handle(handler) {
    if (state === PENDING) {
      handlers.push(handler);
    } else {
      if (state === FULFILLED &&
        typeof handler.onFulfilled === 'function') {
        handler.onFulfilled(value);
      }
      if (state === REJECTED &&
        typeof handler.onRejected === 'function') {
        handler.onRejected(value);
      }
    }
  }

  this.done = function (onFulfilled, onRejected) {
    // ensure we are always asynchronous
    setTimeout(function () {
      handle({
        onFulfilled: onFulfilled,
        onRejected: onRejected
      });
    }, 0);
  }

  doResolve(fn, resolve, reject);
}
複製程式碼

this.done方法做的事情:執行了handle方法,並傳入引數,其中引數就是第一個是成功時候回撥函式,第二個是reject時候的回撥函式。

現在讓我們實現.then吧~

this.then = function (onFulfilled, onRejected) {
  var self = this;
  return new Promise(function (resolve, reject) {
    return self.done(function (result) {
      if (typeof onFulfilled === 'function') {
        try {
          return resolve(onFulfilled(result));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return resolve(result);
      }
    }, function (error) {
      if (typeof onRejected === 'function') {
        try {
          return resolve(onRejected(error));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return reject(error);
      }
    });
  });
}
複製程式碼
  • .then 和.done一樣,接受兩個引數,第一個是成功時候回撥函式onFulfilled,第二個是失敗時候回撥函式onRejected。
  • .then 能實現鏈式呼叫,就是因為其返回了一個promise。

相關文章