渐进式完成一个A+规范的promise

577 阅读7分钟

image.jpg
这里有一堆关于promise的测试题,大家可以检验一下自己是否对promise足够了解,建议对promise有了比较充分的了解之后再尝试手写promise
promiseA+规范

let p = new Promise((resolve,reject)=>{
    try {
        ...
        resolve(val)
    } catch(err) {
        reject(err)
    }
    
})
p.then((val)=>{
    ...
})

按照基本的用法,promise的基本结构应该是这样的

class Promise{
  // 构造器
  constructor(executor){
    let resolve = () => { };
    let reject = () => { };
    executor(resolve, reject);
  }
  then(onFulfilled,onRejected){}
}

解释一下:

  • promise接受一个函数作为参数,规范里叫executor(执行者的意思)
  • executor这个函数接受两个函数作为参数,分别是resolve和reject,这两个函数实际上是在promise函数内部定义的,具体的实现和作用请继续往下看


规范在很大程度上表示了promise的设计原理,我们按照A+规范一步步完善它。也许在这个过程中你可能会对其中一些实现产生疑问,但是不要着急,等到完整的实现之后你就会恍然大悟

Promise 的状态

  • Pendding: 初始状态,并可以转化为fulfilled(成功态)和rejected(失败态)
  • Fulfilled: 成功状态,不可转化为其他状态,且必须有一个不可改变的值(value)
  • Rejected:失败状态,不可转化为其他状态,且必须有一个不可改变的据因(reason)


promise1.0

class MyPromise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
      }
    };
    // 如果executor执行报错,直接执行reject
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
}

then方法

  • 一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。promise 的 then 方法接受两个参数:
promise.then(onFulfilled, onRejected)
  • then 方法可以被同一个 promise 调用多次

当状态为fulfilled(成功态)时执行onFulfilled,当状态为rejected时,执行onRejected,那么结合then的调用方式,可以将then这样实现:

class MyPromise {
  constructor(executor){...}
  then(onFulfilled,onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
  }
}

实现异步调用

这样已经实现了一个promise的基本雏形,但是现在它还不能实现异步调用,例如:

new MyPromise((resolve,reject)=>{
	setTimeout(()=>{
    console.log(123)
    resolve()
  },100)
}).then(()=>{
  console.log(456)
})

你会发现输出也不符合预期,因为这里的resolve是异步执行的,所以resolve是在then之后发生的,state还是pendding。所以我们作出以下改造

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());   // 
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
    // 当状态state为pending时,将两个函数存起来
    if (this.state === 'pending') {
      this.onResolvedCallbacks.push(()=>{
        onFulfilled(this.value);
      })
      this.onRejectedCallbacks.push(()=>{
        onRejected(this.reason);
      })
    }
  }
}

仿照发布订阅的思想,使用一个数组将then里面的两个函数储存起来,等异步任务开始的时候,在执行这两个函数。为什么使用数组呢?因为规范中还有一条就是:一个promise可以调用多个then,所以每次调用then的时候将这个函数参数push到数组中,resolve或者reject时foreach执行他们,例如:

// 多个then的情况
let p = new Promise();
p.then();
p.then();

另外我们必须保证then中的两个函数参数onFulfilled和onRejected是最后执行的,因此我们应该把它们放在异步队列当中,实现方法就是用setTimeout包起来即可

...
if (this.state === 'pending') {
  this.onResolvedCallbacks.push(()=>{
    setTimeout(()=>{
      onFulfilled(this.value);
    },0)
  })
  this.onRejectedCallbacks.push(()=>{
    setTimeout(()=>{
      onRejected(this.reason);
    },0)
  })
}

链式调用

promise最大的作用就是链式调用,是为了解决回调地狱的问题。接下来实现链式调用
实现的原理就是在then函数中返回一个promise,即

promise2 = promise1.then(onFulfilled, onRejected);

按照A+规范:

  • 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程[[Resolve]](promise2, x)
  • 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
  • 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
  • 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因

那么按照规范,我们应该比较x和promise的值,因此我们应该实现一个resolvePromise(promise,x,resolve,reject) 这样一个函数,
因此then方法实现应该是这样的:

···
let promise2 = new Promise((resolve, reject)=>{
      if (this.state === 'fulfilled') {
        let x = onFulfilled(this.value);
        // resolvePromise函数,处理自己return的promise和默认的promise2的关系
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'rejected') {
        let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(()=>{
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.onRejectedCallbacks.push(()=>{
          let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    });
    // 返回promise,完成链式
    return promise2;
  }
}

接下来主要就是resolvePromise的实现过程:
规范中规定了三种情况:x与promise相等;x为promise;x为对象或者函数。
Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),运行 [[Resolve]](promise, x) 需遵循以下步骤:【参考Promise 解决过程】。
x 与 promise 相等
如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise。

  1. x 为 Promise
  • 如果 x 为 Promise ,则使 promise 接受 x 的状态。
  • 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝。
  • 如果 x 处于执行态,用相同的值执行 promise。
  • 如果 x 处于拒绝态,用相同的据因拒绝 promise。
  1. x 为对象或函数

如果 x 为对象或者函数:

  • 把 x.then 赋值给 then。
  • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise。
  • 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
    • 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
    • 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
    • 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
    • 如果调用 then 方法抛出了异常 e:
      • 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
      • 否则以 e 为据因拒绝 promise
    • 如果 then 不是函数,以 x 为参数执行 promise
  • 如果 x 不为对象或者函数,以 x 为参数执行 promise

如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise 。

function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(new TypeError('循环引用'));
  }
  if (x instanceof Promise) {
    if (x.state === PENDING) {
      x.then(
        y => {
          resolvePromise(promise2, y, resolve, reject);
        },
        reason => {
          reject(reason);
        }
      );
    } else {
      x.then(resolve, reject);
    }
  } else if (x && (typeof x === 'function' || typeof x === 'object')) {
    // 避免多次调用
    let called = false;
    try {
      //把 x.then 赋值给 then
      let then = x.then;
      if (typeof then === 'function') {
        // 如果 then 是函数,将 x 作为函数的作用域 this 调用之。
        // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
        // 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
        then.call(
          x,
          // 如果 resolvePromise 以值 y 为参数被调用,则运行[[Resolve]](promise, y)
          y => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
          r => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      }else {
        // 如果 then 不是函数,以 x 为参数执行 promise
        resolve(x);
      }  
    } catch (e) {
      // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
      // 如果调用 then 方法抛出了异常 e:
      // 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
      // 否则以 e 为据因拒绝 promise
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 如果 x 不为对象或者函数,以 x 为参数执行 promise
    resolve(x);
  }
}

最后我们来完成promise的其他api:

Promise.reject = function(val){
  return new Promise((resolve,reject)=>{
    reject(val)
  });
}
//race方法 
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data,resolve){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject)=>{
    for(let j=0;j<promises.length;j++){
      promises[i].then(data=>{
        processData(j,data,resolve);
      },reject);
    };
  });
}

参考:
juejin.cn/post/684490…
juejin.cn/post/684490…
juejin.cn/post/684490…