Promise如何实现?

739 阅读6分钟

背景

经常用Promise完成前端web请求,如处理fetchAPI封装的一些网络请求结果时候会调用Promise的相关API,它的出现是为了解决回调地狱问题,但是Promise的内部是如何实现的呢?带着这个疑问,也是一道面试题,有了这篇文章

Promise的三种状态

Promise对象的构造语法如下:

let promise = new Promise(function(resolve, reject) {
  // executor 函数体
});

传递给Promise构造函数的function称为executor,当Promise对象被创建的时候,它会立即调用;
其内部其实就是一个状态机,有三种状态pending、fulfilled、rejected,分别对应着等待、成功、拒绝

promise-resolve-reject.svg

从状态图变化可以看出,promise的executor函数只会调用resolve或reject,promise的最后状态一定变化,而且只有返回结果或返回错误一种可能。

Promise链

通常我们实践Promise都是链式调用的,之所以能够发生链式调用是因为promise.then又返回了一个promise,新手常犯的一个错误就是将几个处理程序handler添加到一个Promise上,以为它们之间会相互传递数据,但其实它们各自独立运行:

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

示例代码中所有的promise.then都会返回相同的结果,实践中虽然很少这么使用,但也是个我之前不知道的点。

promise-then-many.svg

Promise错误处理

异步操作失败是在所难免的,如果出现错误,相应的promise就会reject,当失败时程序就交由最近的rejection处理函数:

fetch('www.test.com')
	.then(response => response.json())
	.catch(err => alert(err))   //解析json发生错误时,交由最近的catch处理
  .catch(err2 => alert(err2)); 

相当于Promise在执行executor函数时候,函数体内有一个隐形的try...catch,当异常发生时即被捕获,所以下面代码处理结果相同。

new Promise((resolve, reject) => {
  throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!

new Promise((resolve, reject) => {
  reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!

如果错误发生时候,代码没有调用catch捕获,那么javascript引擎就会跟踪此类rejection,抛出一个全局的错误,至少控制台会打印出错信息。
在浏览器中,可以使用unhandledrejection事件来捕获:

window.addEventListener('unhandledrejection', function(event) {
  // the event object has two special properties:
  alert(event.promise); // [object Promise] - 产生错误的 promise
  alert(event.reason); // Error: Whoops! - 未处理的错误对象
});

new Promise(function() {
  throw new Error("Whoops!");
}); // 没有 catch 处理错误

PromiseAPI

Promise类有5种常用静态方法:resolve、reject、all、allSettled、race

  • resolve  给定值返回resolved promise
let promise = Promise.resolve(value);
//等价于
let promise = new Promise(resolve => resolve(value));
  • reject 返回一个带有error的rejected promise
let promise = Promise.reject(error);
//等价于
let promise = new Promise((resolve, reject) => reject(error));
  • all 需要并行执行promises的时候
let promise = Promise.all([...promises...]);

需要注意的是,返回结果和promises中的顺序是相对的,即使一个promise需要很长时间来resolve,它仍然是结果数组中的一个;并且如果其中一个promise的结果为rejected,则整个Promise.all返回的promise会立即处理这个rejected error,同时其他promise就被忽略,举个例子假如我们同时进行了多个fetch操作,其中一个失败了,其他的fetch操作仍然会执行,但是Promise.all的返回结果会忽略它们的结果;Promise.all不能取消,因为promise中没有cancellation的设计;
通常给all的传参都是数组,但是也可以传可迭代类型的集合,但是如果这些对象中任意一个不是promise,它将直接被包装进入Promise.resolve

//all的参数可以接受可迭代的promise集合
let promise = Promise.all(iterable)

Promise.all([
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000)
  }),
  2, // 视为 Promise.resolve(2)
  3  // 视为 Promise.resolve(3)
]).then(alert); // 1, 2, 3
  • allSettled 新增的特性,旧浏览器需要polyfills支持,和all不同的是可以等待所有的promise被处理 ,也就是说即使一个被reject,仍然会等待其他的promise
//polyfill
if(!Promise.allSettled) {
  Promise.allSettled = function(promises) {
    return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({
      state: 'fulfilled',
      value: v,
    }), r => ({
      state: 'rejected',
      reason: r,
    }))));
  };
}
  • race 赛马机制,哪个promise最先处理完,就返回哪个promise的处理结果,忽略其他
let promise = Promise.race(iterable);

如何实现一个Promise类?

promise的基本框架

class MyPromise {
    constructor(executor) {
        this.value = null
        this.error = null

        const resolve = (value) => {
            this.value = value; 
        }

        const reject = (error) => {
            this.error = error;
        }
        // 外层控制异常捕获
        try {
            executor(resolve, reject);
        } catch(error) {
            reject(error)
        }
    }
}

export default MyPromise;

状态机的实现

const PENDING = "pending";
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
lass MyPromise {
    constructor(executor) {
        this.value = null;
        this.error = null;
        this.state = PENDING;

        const resolve = (value) => {
            if (this.state == PENDING) {
                this.state = FULFILLED;
                this.value = value;
            }
        }

        const reject = (error) => {
            if (this.state == PENDING) {
                this.state = REJECTED;
                this.error = error;
            }
        }
				...
    }
}

module.exports = MyPromise;

then方法的实现

const PENDING = "pending";
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
lass MyPromise {
    constructor(executor) {
      ...
    }

    then(onFulfilled, onRejected) {
        if (this.state == FULFILLED) {
            onFulfilled(this.value);
        } else if (this.state == REJECTED){
            onRejected(this.error);
        }
    }
}

module.exports = MyPromise;

异步调用的实现

上面代码在同步执行下可以正确运行,但是在异步运行下就有问题了

//异步测试case 
const Promise = require('./myPromise');

const promise = new Promise((resolve, reject) => {
    // resolve(1)
    setTimeout(() => {
        console.log('gogogo'); 
        resolve(2);
    }, 1000)
});
//由于then方法先于setTimeout执行,而此时promise的state还是pending,
//因此then方法中的逻辑永远不会执行
promise.then((value) => {
    console.log(value);
}, (error) => {
    console.log(error);
});

解决方案:

class MyPromise {
    constructor(executor) {
	...同上
      	//创建回调数组
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.state == PENDING) {
                this.state = FULFILLED;
                this.value = value;
                this.onFulfilledCallbacks.forEach((onFulfillCallback) => {
                    //轮寻
                    onFulfillCallback(this.value);
                })
            }
        }

        const reject = (error) => {
            if (this.state == PENDING) {
                this.state = REJECTED;
                this.error = error;
                this.onRejectedCallbacks.forEach((onRejectedCallback) => {
                    //轮寻
                    onRejectedCallback(this.error);
                })
            }
        }
        ...同上
    }

    then(onFulfilled, onRejected) {
 	//异步调用下,state仍是pending,因此走到最后else逻辑 
        if (this.state == FULFILLED) {
            onFulfilled(this.value);
        } else if (this.state == REJECTED){
            onRejected(this.error);
        } else {
            //将回调函数保存在回调数组中   
            this.onFulfilledCallbacks.push(onFulfilled)
            this.onRejectedCallbacks.push(onRejected)
        }
    }
    
}

链式调用的实现

显然then方法中需要返回一个新的promise,并且要将上一个promise的结果带入

then(onFulfilled, onRejected) {
    const nextPromise = new MyPromise((resolve, reject) => {
        if (this.state == FULFILLED) {
            onFulfilled(this.value);
        } else if (this.state == REJECTED){
            onRejected(this.error);
        } else {
            this.onFulfilledCallbacks.push(onFulfilled)
            this.onRejectedCallbacks.push(onRejected)
        }
    })
    return nextPromise;
}

上面只是链式调用的基本框架,内部处理逻辑相似,只是返回一个新的Promise,但是没有将上一个处理的结果带入,并传递到下个resolve或reject
下面看下完整代码,当上次返回结果是一个新的Promise类型时,需要递归调用

resolvePromise(nextPromise, lastResult, resolve, reject) {
  if (nextPromise == lastResult) {
      reject(new TypeError('Chaining Cycle'));
  } 
  if (lastResult && typeof lastResult === 'object' || typeof lastResult === 'function') {
      let used;
      try {
          //如果还是一个promise
          let then = lastResult.then; 
          if (typeof then === 'function') {
              then.call(lastResult, (result) => {
                  if (used) return;
                  used = true;
                  this.resolvePromise(nextPromise, result, resolve, reject);
              }, (err) => {
                  if (used) return;
                  used = true;
                  reject(err);
              })
          } else {
              if (used) return;
              used = true;
              resolve(lastResult)
          }
      } catch(error) {
          if (used) return;
          used = true;
          reject(error);
      }
  } else {
      resolve(lastResult);
  }
}

then(onFulfilled, onRejected) {
  const nextPromise = new MyPromise((resolve, reject) => {
      if (this.state == FULFILLED) {
          try {
              let lastResolved = onFulfilled(this.value)
              this.resolvePromise(nextPromise, lastResolved, resolve, reject);
          } catch (error) {
              reject(error);
          }
      } else if (this.state == REJECTED){
          try {
              let lastRejected = onRejected(this.error);
              this.resolvePromise(nextPromise, lastRejected, resolve, reject);
          } catch(error) {
              reject(error);
          }
      } else {
          this.onFulfilledCallbacks.push(()=>{
              try {
                  let lastResolved = onFulfilled(this.value)
                  this.resolvePromise(nextPromise, lastResolved, resolve, reject);
              } catch (error) {
                  reject(error);
              }
          });
          this.onRejectedCallbacks.push(() => {
              try {
                  let lastRejected = onRejected(this.error);
                  this.resolvePromise(nextPromise, lastRejected, resolve, reject);
              } catch(error) {
                  reject(error);
              }
          });
      }
  })
  return nextPromise;
}

异常处理的实现

catch方法其实只是then方法的一次特殊调用

catch(onRejected){ 
  return this.then(null, onRejected);
}

附件:完整代码