JavaScript:手写Promise

109 阅读3分钟

手撕Promise

/**
 * 手写Promise源码
 *  协商命名法则   _开头标识当前方法为内部方法,不提供给外部使用,无法做强制命名规范
 *
 * @format
 */

/**
 * Promise使用方式
 * const pro = new Promise((resolve,reject)=>{
 * })
 * pro.then(res=>res)
 * pro.then(d=>d)  then可以调用多次
 * pro.catch(e=>e)
 * Promise.all([p1,p2,p3]).then(res=>[p1res,p2res,p3res])
 * Promise.resolve()
 * 根据使用场景分析
 * 1,Promise 构造函数参数为一个函数,并给函数传递两个函数参数
 * 2,Promise 实例方法有,then、catch
 * 3,Promise 静态方法 resolve、reject、all
 * 4,Promise 实例是有状态的  pending fuilled reject
 * */

/* 根据使用分析,并使用 ES6 class 构建自己的Promsie构造函数 */
/* 统一设置静态变量,内部使用变量对比 */
const PENDING = 'pending';
const RESOLVE = 'fuilled';
const REJECT = 'reject';
/**
 * 判断是否符合PromiseA+ 规范
 * @param pro promise
 */
function isPromise(pro) {
 return !!(pro && typeof pro === 'object' && pro.then && typeof pro.then === 'function');
}
class $Promise {
 /**
  *
  * @param {Function} executor
  */
 constructor(executor) {
  // 设置初始状态
  this._state = PENDING;
  //   设置初始值
  this._value = null;
  // 由于 then 是可以多次调用的,该值用于绑定 then 的回调函数
  this._handles = [];
  // 函数一旦执行报错了,自动变为失败的状态
  try {
   /**
    * 为什么要使用 bind 返回一个 新的函数
    * 因为在使用的时候是直接使用执行的
    * 在执行上下文中,this指向了 window,严格模式下会指向 undefined
    * */
   executor(this._resolved.bind(this), this._reject.bind(this));
  } catch (error) {
   this._reject(error);
  }
 }
 /**
  *
  * @param executor then中添加的函数
  * @param state    该函数在什么状态下执行
  * @param resolve  让 then 函数返回的 promise 返回成功
  * @param reject   让 then 函数返回的 promise 返回失败
  * resolve reject  执行后让then后面的then可以链式调用
  */
 _pushHandler(executor, state, resolve, reject) {
  this._handles.push({
   executor,
   state,
   resolve,
   reject,
  });
 }
 /**
  * 执行回调函数队列
  */
 _runHandlers() {
  // 只有状态改变了才能执行
  if (this._state === PENDING) return;
  while (this._handles[0]) {
   this._handleRunTime(this._handles[0]);
   // 执行完成后需要将当前的回调删除
   /**
    * 不删除会出现多次调用问题
    * 场景
    * Pro.then(function A(){})
    * Pro.then(function B(){})
    * 二次then的时候会执行一次的回调
    */
   this._handles.shift();
  }
 }
 /**
  *
  * @param handle 处理单个函数执行结果
  */
 _handleRunTime({ executor, state, resolve, reject }) {
  // 需要放至到微任务队列中,该方法就不做了,待后续更新
  // 记录了多个回调函数,包括成功和失败的,如果成功了就不需要执行失败
  if (this._state !== state) return;
  // 根据规范,如果回调函数不是一个函数,需要将值透传的到下一个
  // 一旦发生 reject 最近的catch 能捕获到,后续的 catch不能捕获到这次错误
  // 后续的 catch 捕获的新的Promise
  if (typeof executor !== 'function') {
   return this._state === RESOLVE ? resolve(this._value) : reject(this._value);
  }
  //   一旦在回调函数中执行错误了,将新的Promise自动返回 失败
  try {
   const result = executor(this._value);
   if (isPromise(result)) {
    return result.then(resolve, reject);
   } else {
    resolve(result);
   }
  } catch (error) {
   reject(error);
  }
 }
 /**
  * 优化代码减少冗余代码,提取公共代码
  * 记录并修改状态和数据
  * @param state 用于改变当前Promise的状态
  * @param value 记录执行的结果,并传入then的回调函数中
  */
 _changState(state, value) {
  // 状态不允许多次改变
  if (this._state !== PENDING) return;
  this._state = state;
  this._value = value;
  // 状态发生改变执行 then catch 回调函数
  this._runHandlers();
 }
 /**
  *
  * @param {any} data 任务完成时的数据
  */
 _resolved(data) {
  this._changState(RESOLVE, data);
 }
 /**
  *
  * @param reson  任务失败时的数据
  */
 _reject(reson) {
  this._changState(REJECT, reson);
 }
 /**
  *
  * @param onFuilled  任务成功时执行的函数
  * @param onRejected 任务失败时执行的函数
  */
 then(onFuilled, onRejected) {
  // 根据规范 then 会返回一个新的Promise
  //   做一个记录 外部的Promsie 叫 A 新返回的Promise 叫 B
  const _that = this;
  return new $Promise((resolve, reject) => {
   /* 由于 then 中的函数
    * 需要在 A 参数(executor)函数执行 _resolve,_reject 方法后执行
    * 故先将 回调函数记录下来
    */
   // 在这个里面使用 this 类似使用that
   // 当前的this还是还是指向 _that
   this._pushHandler(onFuilled, RESOLVE, resolve, reject);
   this._pushHandler(onRejected, REJECT, resolve, reject);
   // 这个promise的 resolve 什么时候执行呢
   // 将执行函数加入到函数执行队列中
   // 并查看当前状态是否完成并执行回调
   this._runHandlers();
  });
 }
 /**
  *
  * @param onRejected 任务失败时执行的函数
  */
 catch(onRejected) {
  // 根据规范 cath 会返回一个新的Promise
  // 也符合 then 的逻辑,所以只给 then 传入失败的函数就可以了
  return this.then(undefined, onRejected);
 }
 /**
  * 简单写
  */
 static resolve(value) {
  return new $Promise((resolve) => resolve(value));
 }
 static reject(reson) {
  return new $Promise((resolve, reject) => reject(reson));
 }
 /**
  *
  * @param promise Promise数组
  */
 static all(promiseArray) {
  const len = promiseArray.length;
  // 记录完成的Promise 数量
  let succcessIndex = 0;
  // 缓存每次完成返回的结果
  let successResult = new Array(len);
  return new $Promise((resolve, reject) => {
   // 如果是var的话,那就需要使用到闭包记录当前循环的索引
   for (let index = 0; index < len; index++) {
    const element = promiseArray[index];
    // 问题 如果 element 不是 Promise 类型怎么操作
    element
     .then((res) => {
      successResult[index] = res;
      succcessIndex += 1;
      if (succcessIndex === len) {
       resolve(successResult);
      }
     })
     .catch(reject);
   }
  });
 }
}

如有错误请联系,请大神勿喷,个人学习总结体会

  // 执行顺序面试题
  // 微任务也是先是按顺序执行
   // 同步任务完成后立即执行该微任务回调
   Promise.resolve().then(() => {
    //异步代码
    console.log(1);
   });
   // Promise 对象是 pending 状态 // 同步任务
   new Promise((resolve, reject) => {
    console.log('我是PromiseNew立即执行 2');
    resolve();
   })
    .then(() => {
     // Promise  Pending   Promise 成功后 then的函数才会加到微任务回调队列里
     console.log('我是new 执行then里的console 3');
     // return new Promise((resolve, reject)=>{
     //     console.log('我是new 执行then里的new Promise 4 ')
     //     resolve()
     // }) //
    //  如果返回的是 Promsie 那么源码执行时,会增加一个 then 的回调
    //等同于 return Promise.resolve(1)
    return new Promise(resolve=>resolve(1)).then(res=>{
        console.log('我要打印的值:3的new Promise',res);
     }) //  Promise.resolve().then(()=>{4})
     // newPromise  根据返回值Promise的状态决定
     //  Promise.resolve().then(()=>newPromise)
    })
    .then(
     (res) => {
      // 同步代码,T1 Pending 状态
      console.log('我是new 执行then里的then 5',res);
     },
     (err) => {
      console.log('我是new 执行then里的then 5 err');
      return 555;
     }
    )
    .then((res) => {
     console.log('我是new 执行then里的then 6', res);
    })
    .catch((res) => {
     console.log('我是new 执行then里的catch ', res);
    });
   // 将函数放置到微任务队列中
   // Promise.resolve()同步代码  fulfilled 成功状态
   Promise.resolve()
    .then((res) => {
     // T2
     console.log('我是resolve里的console 7');
     // console.log(2222)
    })
    .then(() => {
     // Promise
     console.log('我是resolve里的console 7 的then  console 8');
    });

执行结果

image.png

function test2() {
    return new Promise(function P1(resolve) {
     console.log(1); //1
     new Promise(function P2(resolve) {
      console.log(2); //2
      setTimeout(function set1() {
       //处理setTimeOut
       resolve(3);
       console.log(4); //4
      });
     }).then(function then1(res) {
      setTimeout(function set2() {
       //处理setTimeOut
       console.log(5);
      });
      console.log(res); //3
     });
     setTimeout(function set2() {
      // 处理setTimeOut
      resolve(6);
      console.log(7); //7
     });
    }).then(function then2(res) {
     // 等待
     console.log(res); // 6
     setTimeout(function set3() {
      // 处理setTimeOut
      console.log(8);
     });
     console.log(9); // 9
    });
   }

执行结果

image.png

感谢说明

渡一前端提薪课