rwson

rwson

一个前端开发

实现你自己的Promise

在现代化前端开发中,经常会用到Promise模式,Promise最大的好处就是可以使异步代码看起来如同步般清新易读,从而从回调地狱中解脱出来,ES6已经原生支持Promise对象,但在未支持的浏览器中还需要通过 polyfill 模拟实现。下面一起实现一个Promise

一般我们用Promise会写成类似下面的样子:

const ins = new Promise((resolve, reject) => {
  	//	...
});

ins.then((res) => {
  //   ...
}, (ex) => {});

Promise中一共存在三种状态,PENDING, FULFILLED,REJECTED,在实例化一个Promise后,它的状态会变成PENDING,执行resolve或者reject方法会把状态改成FULFILLED或者REJECTED,此过程不可逆,也就是说每个Promise只能调用一次resolve或者reject

先来搭个骨架:

const PENDING = "PENDING",
    FULFILLED = "FULFILLED",
    REJECTED = "REJECTED";

function Promise(resolver) {
    if (!isFunction(resolver)) {
        throw new TypeError("TypeError: resolver must be a function");
    }

    //	实例的值
    this.value = null;

    //	实例的状态
    this.status = PENDING;

    //	缓存then和catch中传入的方法
    this._doneCallbacks = [];
    this._failCallbacks = [];

    //	在执行resolver内部抛出异常, try ... catch 包裹
    try {
        resolver(resolve.bind(this), reject.bind(this));
    } catch (ex) {
        reject.bind(this)(ex);
    }
}

Promise.prototype = {
  constructor: Promise,
  
  then: function(onFulfilled, onRejected) {},
  
  catch: function(onRejected) {}
};

function isFunction(obj) {
  return typeof obj === "function";
}

上面实现了一个Promise构造器并且在它原型上声明了thencatch两个空方法,下面我们一起来实现一下thencatch两个方法, Promise中的then支持调用,所以then方法需要返回一个Promise实例。

Promise.prototype = {
  constructor: Promise,
  
  //	then方法接受onFulfilled, onRejected两个回调, 并且返回一个新的Promise实例
  then: function(onFulfilled, onRejected) {
    //	要返回的新Promise
    let ins = new Promise(() => {});
	if (isFunction(onFulfilled)) {
      //	将onFulfilled缓存到_doneCallbacks中
      this._doneCallbacks.push(makeCallback(ins, onFulfilled, "resolve"));
    }
    if (isFunction(onRejected)) {
      this._failCallbacks.push(makeCallback(ins, onFulfilled, "reject"));
    }
    return ins;
  },
  
  catch: function(onRejected) {
    return this.then(null, onRejected);
  }
};

在实例化一个Promise时,我们的resolverresolvereject两个参数(也就是上面构造器中的最后),在实例化完成后其中有一个将被立即调用,用于结束当前Promise。下面一起实现下resolvereject,这两个函数完成的功能就是接受resolver中异步处理返回的值并且传递给then或者catch,并且将当前PENDING的状态改掉,刚才上面说到Promise的状态从PENDINGFULFILLED或者REJECTED是一个不可逆的过程,所以为了确保resolve或者reject不被多次调用,需要在方法开头提前判断下status是不是PENDING状态。

function resolve(data) {
  if (this.status !== PENDING) {
    return;
  }
  
  this.value = data;
  this.status = FULFILLED;
  run.call(this);
}

function reject(ex) {
  if (this.status !== PENDING) {
    return;
  }
  
  this.value = ex;
  this.status = REJECTED;
  run.call(this);
}

resolvereject中,我们都调用了run方法,该方法的作用是用于触发接下来的Promise的执行。run函数中需要注意的一点是,需要异步执行相关的回调函数,下面一起来实现一个run方法,需要注意的是每个Promise只能被执行一次,所以在run方法最后,把_doneCallbacks_failCallbacks置空。

function run() {
  if (this.status === PENDING) {
    return;
  }
  
  //	获取到异步处理后的值, 根据当前状态获取要执行的回调
  const { value } = this,
		callbacks = this.status === FULFILLED ? this._doneCallbacks : this._failCallbacks
  
  //	Promise异步执行
  let timeout = setTimeout(() => {
    for (let fn of callbacks) {
	  //	值穿透到每个then或者catch
      fn(value);
    }
    clearTimeout(timeout);
  });
  
  this._doneCallbacks = [];
  this._failCallbacks = [];
}

上面run里执行的每个fn都是then方法中push_doneCallbacks_failCallbacks的makeCallback返回值,makeCallback`是整个代码中比较复杂的一部分,下面一起看下具体实现。

function makeCallback(promise, callback, action) {
    return function promiseCallback(value) {
    	//	callback是个函数
        if (isFunction(callback)) {
        	//	定义个变量去接受返回值
        	//	防止抛出异常, try ... catch 包裹, catch 时直接调用reject
            let x;
            try {
                x = callback(value);
            } catch (ex) {
                reject.bind(promise)(ex);
            }

            //	callback中返回的是this, 会引发死循环
            //	抛出类型异常到reject
            /**
             * const ins = new Promise((resolve, reject) => {});
             * ins.then(() => {
             * 		return ins;
             * });
             */
            if (x === promise) {
                let reason = new TypeError("TypeError: The return value could not be same with the promise");
                reject.bind(promise)(reason);
            } else if (x instanceof Promise) {

            	//	callback中返回一个新的Promise
            	//	执行该Promise的then和catch
            	/**
            	 * const ins = new Promise((resolve, reject) => {});
            	 * ins.then(() => {
            	 * 		return new Promise((resolve, reject) => {});
            	 * });
            	 */
                x.then((data) => {
                    resolve.bind(promise)(data);
                }, (ex) => {
                    reject.bind(promise)(ex);
                });
            } else {
                let then;
                (function resolveThenable(x) {
                    // 如果返回的是一个Thenable对象(Promise)
                    if (x && (typeof x === "object" || isFunction(x))) {
                        try {
                            then = x.then;
                        } catch (ex) {
                            reject.bind(promise)(ex);
                            return;
                        }

                        if (isFunction(then)) {
                            // 调用Thenable对象的then方法时,传递进去的resolvePromise和rejectPromise方法(及下面的两个匿名方法)
                            // 可能会被重复调用。 但Promise+规范规定这两个方法有且只能有其中的一个被调用一次,多次调用将被忽略
                            // 此处通过invoked来处理重复调用
                            let invoked = false;
                            try {
                                then.call( x, (y) => {
                                		//	如果已经被调用了,直接return掉
                                        if (invoked) {
                                            return;
                                        }
                                        invoked = true;

                                        // 避免死循环
                                        if (y === x) {
                                            throw new TypeError("TypeError: The return value could not be same with the previous thenable object");
                                        }

                                        // y仍有可能是thenable对象,递归调用
                                        resolveThenable(y);
                                    }, (e) => {
                                        if (invoked) {
                                            return;
                                        }
                                        invoked = true;
                                        reject.bind(promise)(e);
                                    }
                                );
                            } catch (e) {
                                // 如果resolvePromise和rejectPromise方法被调用后,再抛出异常,则忽略异常
                                // 否则用异常对象reject此Promise对象
                                if (!invoked) {
                                    reject.bind(promise)(e);
                                }
                            }
                        } else {
                            resolve.bind(promise)(x);
                        }
                    } else {
                        resolve.bind(promise)(x);
                    }
                }(x));
            }
        } else {
        	//	根据action判断执行resolve或者reject
        	action === "resolve" ? resolve.bind(promise)(value) : reject.bind(promise)(value);
        }
    };
}

上面就是我对于一个Promise的实现,完整代码请移步我的GitHub

参考: