带你手写 Promise,别再死记硬背了!!!

1,090 阅读12分钟

一、动机

Promise 是目前处理异步操作中必不可少的内容,即使你使用了 async await ,但最后得到的还是个被 Promise 包裹的内容,例如:

image.png

因此,无论是在实际学习、工作或者面试中,都无法避免 Promise 的相关使用和原理,特别是在面试中甚至会让你自己实现一个 Promise. 本文主要介绍 Promise 的一些使用和特性,并使用自己的代码去实现它对应的功能,当然注重的应该是对实现过程的理解,而不是死记硬背!!!

二、Promise 前置知识

要想要深刻了解某个知识,前提条件是:你可以不是很明白其中原理,但是你必须知道如何正常使用?因此,建议对不了解的知识,首先得学会基本使用,然后在从使用层面去接触底层原理。

基本介绍

  • Promise 实例拥有三个状态:【pending、fulfilled、rejected】. 并且这三种状态只能从 【pending --> fulfilled】 或 【pending --> rejected】, 也就是说只要当前状态不是 pending ,那么当前状态就不能被改变.

image.png image.png

  • 1. 实例化 Promise 时,可以接收一个函数作为参数,这个函数可以接收到 resolvereject 两个实例方法,用于更改当前实例的状态,并把它们接收到的参数传递给下一个 then 对应的参数中.
    • PS: Promise 中的回调是被同步执行的,但是 then 操作属于异步执行
  • 2. then 方法可以接收两个函数作为参数,第一个函数相当于执行上一个 promise 中执行 resolve 后对应的回调,第二个函数相当于执行上一个 promise 中执行 reject 后对应的回调.
  // 1. 实例化 Promise 时
// 可以接收一个函数作为参数,这个函数可以接收到 resolve 和 reject 两个实例方法
// 用于更改当前实例的状态,并把它们接收到的参数传递给下一个 then 对应的参数中
  new Promise((resolve, reject) => {
    // resolve 和 reject 可以都执行,但都执行的意义不大,因为 promise 状态发生更改后,就不能在被更改
    resolve('ok');
    // reject('err');
  }).then((value) => {
    console.log("resolve callback = ", value); // 若执行 resolve,则 value = ok
  }, (reason) => {
    console.log("reject callback = ", reason); // 若执行 reject,则 value = err
  });

  // 2. then 方法可以接收两个函数作为参数:
  // 第一个函数相当于执行上一个 promise 中执行 resolve 后对应的回调
  // 第二个函数相当于执行上一个 promise 中执行 reject 后对应的回调
  • 3.then 的两个回调函数中返回的值,默认可以被下一个 then 中的第一个回调函数接收到,其实表明了,then 操作之后会得到一个新的 promise,并且这个新的 promise 的状态默认 fulfilled.
// 3. 在 then 的两个回调函数中返回的值,默认可以被下一个 then 中的第一个回调函数接收到
 // 其实表明了,then 操作之后会得到一个新的 promise,并且这个新的 promise 的状态默认 fulfilled
 let p1 = new Promise((resolve, reject) => {
    resolve('ok');
    // reject('err');
  });
  
 let p2 = p1.then((value) => {
    console.log("resolve p1.then = ", value); // ok
    return 'then ok';
  }, (reason) => {
    console.log("reject p1.then = ", reason);// err
    return 'then err';
  })
  
  p2.then((value) => {
    console.log("resolve p2.then = ", value);// ok || err
  }, (reason) => {
    console.log("reject p2.then = ", reason);
  });
  • 4. then 中允许返回 Promise 实例,但不允许返回同一个 Promise 实例. 换句话说,不能在同一个 Promise 实例的 then 操作中返回它自己.
  • 5. then 方法中只要设置了上一个 Promisereject 对应的回调,那么就可以正常接收到错误信息,否则按 JavaScript报错机制显示.
   // 4. then 中允许返回 Promise 实例,但不允许返回同一个 Promise 实例
   // 换句话说,不能在同一个 Promise 实例的 then 操作中返回它自己
   let p1 = new Promise((resolve, reject) => {
      resolve('ok');
      // reject('err');
    });
    
   let p2 = p1.then((value) => {
      console.log("resolve p1.then = ", value); // ok
      return p2;
    }, (reason) => {
      console.log("reject p1.then = ", reason);// err
      return p2;
    })
    
    p2.then((value) => {
      console.log("resolve p2.then = ", value);// ok || err
    }, (reason) => {
      console.log("reject p2.then = ", reason);// TypeError: Chaining cycle detected for promise #<Promise>
    });
    // 5. then 方法中只要设置了上一个 Promise 中 reject 对应的回调,那么就可以正常接收到错误信息,否则按 JavaScript 的报错机制显示. 
   
  • 6. Promise.all() 以数组形式接收多个 Promise ,当所有的 Promise 执行完成,且状态都为 fulfilled 时,将他们的执行结果以数组形式返回给下一个 then 操作中的 第一个函数回调,否则将发生错误的 Promise 结果,返回第二个函数回调.
    // 1. p1 p2 都成功
    let p1 = new Promise((resolve, reject)=>{
      setTimeout(()=>{
        resolve('p1 success');
      },1000);
    });

    let p2 = new Promise((resolve, reject)=>{
      setTimeout(()=>{
        resolve('p2 success');
        // reject('p2 fail');
      },2000);
    });

    Promise.all([p1,p2])
    .then(value => {
      console.log('all promise success = ', value); // all promise success =  (2) ["p1 success", "p2 success"]
    },reason => {
      console.log('one promise fail = ', reason);
    });
    
    // 2. p1 成功 p2 失败
      let p1 = new Promise((resolve, reject)=>{
      setTimeout(()=>{
        resolve('p1 success');
      },1000);
    });

    let p2 = new Promise((resolve, reject)=>{
      setTimeout(()=>{
        // resolve('p2 success');
        reject('p2 fail');
      },2000);
    });

    Promise.all([p1,p2])
    .then(value => {
      console.log('all promise success = ', value);
    },reason => {
      console.log('one promise fail = ', reason);// one promise fail =  p2 fail
    });
  • 7. Promise.race() 以数组形式接收多个 Promise ,只要有一个 Promise 先执行完成,无论什么状态,都以这个结果返回给下一个 then 中对应的回调函数.
 let p1 = new Promise((resolve, reject)=>{
      setTimeout(()=>{
        resolve('p1 success');
        // reject('p1 fail');
      },1000);
    });

    let p2 = new Promise((resolve, reject)=>{
      setTimeout(()=>{
        resolve('p2 success');
      },2000);
    });

    Promise.race([p1,p2])
    .then(value => {
      console.log('success = ', value);// success =  p1 success
    },reason => {
      console.log('fail = ', reason);
    });

三、实现 MyPromise

1. 实现基本功能

  • new 操作
  • resovle & reject 方法
  • then 方法
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收⼀个函数参数,该函数会⽴即执⾏
class MyPromise {
  constructor(fn) {
    this.value;
    this.status = PENDING;// 默认状态

    // 这里使用 try catch 捕获中可能发生的错误
    try {
      // 这里必须要绑定 this,否则在外部调用时 this 就不会执行当前实例
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject.bind(this, error)
    }
  }

  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = RESOLVED;
    }
  }

  reject(reason) {
    if (this.status === PENDING) {
      this.value = reason;
      this.status = REJECTED;
    }
  }
}

MyPromise.prototype.then = function (onResolve, onReject) {

  // 上一个 promise 实例调用了 resolve
  if(this.status === RESOLVED){
    onResolve(this.value);
  }

  // 上一个 promise 实例调用了 reject
  if(this.status === REJECTED){
    onResolve(this.value);
  }

}

image.png 现在 MyPromise 已经已经可以处理基本的 resovle 和 reject 操作,包括 then 中也可以正常接受到对应值.

2. 考虑异步改变状态的情况

  • 暴露问题:虽然在【1】 中我们实现了最最最基本的功能,但是如果我们更改状态的方式是异步的,那么会出现什么问题呢?
    • 下面的程序会输出什么呢?答案是啥也不会输出,因为状态是通过 setTimeout 异步修改的,而 then 中的代码全是同步操作,因此,在 setTimeout 中的 resolve 执行之前,then 就已经执行了,而此时 promise 的状态还是 pending,而 panding 状态我们没有处理.
   // 可以思考一下,这样的操作 MyPromise 会有什么输出?
   let p = new MyPromise((resolve, reject) => {
      setTimeout(()=>{
        resolve(1);
      },1000);
    }).then(value => {
      console.log('then resolve = ', value);
    }, reason => {
      console.log('then reject = ', reason);
    });
  • 解决问题:缓存回调,等待执行.
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收⼀个函数参数,该函数会⽴即执⾏
class MyPromise {

  constructor(fn) {
    this.value;
    this.status = PENDING;// 默认状态
    this.onResolveCallBack = [];// 缓存 onResolve 
    this.onRejectCallBack = [];// 缓存 onReject 

    // 这里使用 try catch 捕获中可能发生的错误
    try {
      // 这里必须要绑定 this,否则在外部调用时 this 就不会执行当前实例
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject.bind(this, error);
    }
  }

  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = RESOLVED;
      // 遍历调用 onResolveCallBack
      this.onResolveCallBack.forEach(r => r());
    }
  }

  reject(reason) {
    if (this.status === PENDING) {
      this.value = reason;
      this.status = REJECTED;
      // 遍历调用 onRejectCallBack
      this.onRejectCallBack.forEach(r => r());
    }
  }

}

MyPromise.prototype.then = function (onResolve, onReject) {

  // 当前 promise 实例调用了 resolve
  if (this.status === RESOLVED) {
    onResolve(this.value);
  }

  // 当前 promise 实例调用了 reject
  if (this.status === REJECTED) {
    onReject(this.value);
  }

  // 当前 promise 状态为 pending,把当前的 onResolve & onReject 缓存起来
  if (this.status === PENDING) {
    this.onResolveCallBack.push(() => {
      onResolve(this.value);
    });
    this.onRejectCallBack.push(() => {
      onReject(this.value);
    });
  }

}

3. 实现 then 链式调用 & then 穿透 & 异步操作 & 错误捕获

  • PS:then 穿透指当某个 then 操作中没有对应的回调处理,把当前 promise 的值透传给一下个 then. 例如:

image.png

  • 问题暴露:
    • 未实现多个 then 间的链式调用
    • 未实现 then 穿透
    • 未实现 resolve & reject 异步
    • 未捕获所有回调函数可能出现的错误,并传递给下一个 then 的 onReject 回调
  • 解决问题:
    • then 中返回一个新的 promise
    • 对 then 中接收的参数做兼容处理
    • 调用回调时,通过 setTimeout 包裹处理
    • 通过 try catch 实现错误捕获,然后调用 promise 中的 reject
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收⼀个函数参数,该函数会⽴即执⾏
class MyPromise {

  constructor(fn) {
    this.value;
    this.status = PENDING;// 默认状态
    this.onResolveCallBack = [];// 缓存 onResolve 
    this.onRejectCallBack = [];// 缓存 onReject 

    // 这里使用 try catch 捕获中可能发生的错误
    try {
      // 这里必须要绑定 this,否则在外部调用时 this 就不会执行当前实例
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject.bind(this, error);
    }
  }

  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = RESOLVED;

      // setTimeout 为了保证异步顺序执行
      setTimeout(() => {
        // 遍历调用 onResolveCallBack
        this.onResolveCallBack.forEach(r => r());
      });
    }
  }

  reject(reason) {
    if (this.status === PENDING) {
      this.value = reason;
      this.status = REJECTED;

      // setTimeout 为了保证异步顺序执行
      setTimeout(() => {
        // 遍历调用 onRejectCallBack
        this.onRejectCallBack.forEach(r => r());
      });
    }
  }

}

MyPromise.prototype.then = function (onResolve, onReject) {
  // 保证 onResolve & onReject 为函数
  // 主要是为了 .then().then((v)=>v) 的情况,称之为 then 穿透
  onResolve = typeof onResolve === 'function' ? onResolve : (v) => v;
  onReject = typeof onReject === 'function' ? onReject : (v) => v;

  // 这里是为了实现链式操作
  return new MyPromise((resovle, reject) => {

    // 当前 promise 实例调用了 resolve
    if (this.status === RESOLVED) {
      // setTimeout 为了保证异步顺序执行
      setTimeout(() => {
        try {
          let result = onResolve(this.value);
          resovle(result); // 下一个 promise 的状态为 fulfilled 
        } catch (error) {
          reject(error);
        }
      });
    }

    // 当前 promise 实例调用了 reject
    if (this.status === REJECTED) {
      // setTimeout 为了保证异步顺序执行
      setTimeout(() => {
        try {
          let result = onReject(this.value);
          resovle(result); // 下一个 promise 的状态为 fulfilled
        } catch (error) {
          reject(error);
        }
      });
    }

    // 当前 promise 状态为 pending,把当前的 onResolve & onReject 缓存起来
    if (this.status === PENDING) {
      this.onResolveCallBack.push(() => {
        try {
          let result = onResolve(this.value);
          resovle(result); // 下一个 promise 的状态为 fulfilled 
        } catch (error) {
          reject(error);
        }
      });
      this.onRejectCallBack.push(() => {
        try {
          let result = onReject(this.value);
          resovle(result); // 下一个 promise 的状态为 fulfilled
        } catch (error) {
          reject(error);
        }
      });
    }
  });
}

5. then 中的逻辑判断

  • then 手动返回 promise 实例,下一个 then 操作要依据返回的 promise 状态
  • 不允许返回当前 promise 实例本身
  • 抽取重复逻辑
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收⼀个函数参数,该函数会⽴即执⾏
class MyPromise {

  constructor(fn) {
    this.value;
    this.status = PENDING;// 默认状态
    this.onResolveCallBack = [];// 缓存 onResolve 
    this.onRejectCallBack = [];// 缓存 onReject 

    // 这里使用 try catch 捕获中可能发生的错误
    try {
      // 这里必须要绑定 this,否则在外部调用时 this 就不会执行当前实例
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject.bind(this, error);
    }
  }

  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = RESOLVED;

      // setTimeout 为了保证异步顺序执行
      setTimeout(() => {
        // 遍历调用 onResolveCallBack
        this.onResolveCallBack.forEach(r => r());
      });
    }
  }

  reject(reason) {
    if (this.status === PENDING) {
      this.value = reason;
      this.status = REJECTED;

      // setTimeout 为了保证异步顺序执行
      setTimeout(() => {
        // 遍历调用 onRejectCallBack
        this.onRejectCallBack.forEach(r => r());
      });
    }
  }

}

MyPromise.prototype.then = function (onResolve, onReject) {
  // 保证 onResolve & onReject 为函数
  // 主要是为了 .then().then((v)=>v) 的情况,称之为 then 穿透
  onResolve = typeof onResolve === 'function' ? onResolve : (v) => v;
  onReject = typeof onReject === 'function' ? onReject : (v) => v;

  // 这里是为了实现链式操作
  let promise = new MyPromise((resolve, reject) => {

    // 当前 promise 实例调用了 resolve
    if (this.status === RESOLVED) {
      // setTimeout 为了保证异步顺序执行
      setTimeout(() => {
          let result = onResolve(this.value);
          transferPromiseResult(promise, result, resolve, reject);
      });
    }

    // 当前 promise 实例调用了 reject
    if (this.status === REJECTED) {
      // setTimeout 为了保证异步顺序执行
      setTimeout(() => {
          let result = onReject(this.value);
          transferPromiseResult(promise, result, resolve, reject);
      });
    }

    // 当前 promise 状态为 pending,把当前的 onResolve & onReject 缓存起来
    if (this.status === PENDING) {
      this.onResolveCallBack.push(() => {
          let result = onResolve(this.value);
          transferPromiseResult(promise, result, resolve, reject);
      });
      this.onRejectCallBack.push(() => {
          let result = onReject(this.value);
          transferPromiseResult(promise, result, resolve, reject);
      });
    }
  });

  return promise;
}

// 将上一次的 promise 的值,传递给下一个 then
function transferPromiseResult(promise, result, resolve, reject) {
  // 为了处理当前的 promise 实例,在当前的 then 被返回
  if (promise === result) {
    throw new TypeError('Chaining cycle detected for promise #<MyPromise>');
  }

  try {
    // 如果上一个 then 返回的是 MyPromise 的实例 && 不是同一个 promise 实例
    // 那只需要把 MyPromise 中的处理好的返回值传递给一下 then 即可
    if (result instanceof MyPromise) {
      result.then(resolve, reject);
    } else {
      // 正常结果,传给下一个 then
      resolve(result);
    }
  } catch (error) {
    reject(error);
  }
}

6. 实现 MyPromise.all() & MyPromise.race()

  • 上面我们实现了大部分的功能,这里的 all 和 race 都只需要基于已有功能实现即可.
  • all --> 接收多个 promise 的数组,当全部 promise 执行完,且状态都为 fulfilled,则返回结果集,否则返回失败的结果.
  • race --> 接收多个 promise 的数组,结果取最先完成的 promise 的结果,无论状态是 fulfilled 或者是 rejected.
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收⼀个函数参数,该函数会⽴即执⾏
class MyPromise {
  constructor(fn) {
    this.value;
    this.status = PENDING;
    this.onResolveCallBack = [];
    this.onRejectCallBack = [];

    // 执行传入fn
    try {
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      // 发生错误时,统一用 reject 方法处理
      this.reject(error);
    }
  }

  // 实例方法 resolve
  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = RESOLVED;

      // setTimeout 保证异步执行
      setTimeout(() => {
        // 执行在 then 中存储的回调
        try {
          this.onResolveCallBack.forEach((fn) => fn && fn(this.value));
        } catch (error) {
          this.onRejectCallBack.forEach((fn) => fn && fn(error));
        }
      });
    }
  }

  // 实例方法 reject
  reject(reason) {
    if (this.status === PENDING) {
      this.value = reason;
      this.status = REJECTED;

      // setTimeout 保证异步执行
      setTimeout(() => {
        // 执行在 then 中存储的回调
        this.onRejectCallBack.forEach((fn) => fn && fn(this.value));
      });
    }
  }


  // 静态方法 resolve
  static resolve(value) {
    return new MyPromise((resolve, reject) => {
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
      } else {
        resolve(value);
      }
    });
  }

  // 静态方法 reject
  static reject(value) {
    console.log(value);
    return new MyPromise((resolve, reject) => {
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
      } else {
        reject(value);
      }
    });
  }

  // 静态方法 all
  static all(promiseArr) {
    // 记录每个成功的 promise 结果
    const resolveResultArr = [];
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach(promise => {
        promise.then(value => {
          resolveResultArr.push(value);

          // 当两者的长度一致,表明所有 promise 执行完成,并结果都是成功的
          if (promiseArr.length === resolveResultArr.length) {
            resolve(resolveResultArr);
          }
        }, reason => {
          // 只要一个 promise 失败,就是失败
          reject(reason);
        });
      });
    });
  }

  // 静态方法 race
  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach(promise => {
          promise.then(value => {
            resolve(value);
          }, reason => {
            reject(reason);
          });
      });
    });
  }

}

MyPromise.prototype.then = function (onResolve, onReject) {

  // 保证 onResolve & onReject 为函数
  // 主要是为了 .then().then((v)=>v) 的情况,称之为 then 穿透
  onResolve = typeof onResolve === 'function' ? onResolve : (v) => v;
  onReject = typeof onReject === 'function' ? onReject : (v) => v;

  let promise = new MyPromise((resolve, reject) => {

    if (this.status === RESOLVED) {
      setTimeout(() => {
        // 把上一个 then 中返回的值保存 result
        let result = onResolve(this.value);
        // 传递值下一个 then
        transferPromiseResult(promise, result, resolve, reject);
      });
    }

    if (this.status === REJECTED) {
      setTimeout(() => {
        // 相当于把上一个 then 中返回的值保存 result
        let result = onReject(this.value);
        // 传递值下一个 then
        transferPromiseResult(promise, result, resolve, reject);
      });
    }

    // 在这里通过数组存储对应的回调,方便在状态改变之后进行调用
    if (this.status === PENDING) {
      this.onResolveCallBack.push(() => {
        // 把上一个 then 中返回的值保存 result
        let result = onResolve(this.value);
        // 传递值下一个 then
        transferPromiseResult(promise, result, resolve, reject);
      });

      this.onRejectCallBack.push(() => {
        // 相当于把上一个 then 中返回的值保存 result
        let result = onReject(this.value);
        // 传递值下一个 then
        transferPromiseResult(promise, result, resolve, reject);
      });
    }
  });

  return promise;
}

// 将上一次的 promise 的值,传递给下一个 then
function transferPromiseResult(promise, result, resolve, reject) {
  // 为了处理当前的 promise 实例,在当前的 then 被返回
  if (promise === result) {
    throw new TypeError('Chaining cycle detected for promise #<MyPromise>');
  }

  try {
    // 如果上一个 then 返回的是 MyPromise 的实例 && 不是同一个 promise 实例
    // 那只需要把 MyPromise 中的处理好的返回值传递给一下 then 即可
    if (result instanceof MyPromise) {
      result.then(resolve, reject);
    } else {
      // 正常结果,传给下一个 then
      resolve(result);
    }
  } catch (error) {
    reject(error);
  }
}

完结