手写实现CustomPromise–掌握Promise

Promise对象在现代前端开发工作日常写业务中使用非常广泛,主要用于解决回调地狱的问题。下面通过一点点的实现一个自己的Promise,加强对其的理解以及实践应用。

正常使用Promise对象时,将要执行的方法传入构造函数中,再方法中修改状态后,通过then方法便能在修改状态后执行对应预期的回调函数

const promise = new Promise((resolve,reject)=>{
  // 模拟Ajax请求
    setTimeout(()=>{
        resolve([{id:1,name:"test"}]);
      },2000);
    });

promise.then((res)=>{
    console.log(res);
},(err)=>{
    console.log(err);
});
// 状态为成功的回调函数
// 两秒后输出结果
// [{id:1,name:"test"}]
复制代码

开始动手实现一个自己的Promise之前,需要先确定设计(实现会有很多注释方便理解阅读)

  1. Promise是一个类(本质是语法糖,也可以通过Function去创建,不赘述),构造函数接受一个函数,并且会传入两个实例方法作为参数
  2. Promise中有3种状态:等待中(pending),成功(fulfilled),失败(rejected),确定状态后无法更改,只有pending --> (fulfilled/rejected)
  1. 实例对象的resolvereject方法可用于改变状态,并且需要内部存储传入成功回调的value或者失败回调的reason
  2. 实例对象的then方法接受两个函数做为参数(resolve,reject),用于判断上个Promise的状态,成功调用resolve,失败调用reject
  1. Promise支持链式调用

下面这段包含了完整代码,但部分api会单独解析

// 定义状态常量,便于引用
const FUFILLED = "fulfilled";
const REJECTED = "rejected";
const PENDING = "pending";

class CustomPromise {
  // executor就是new Promise((resolve,reject)=>{})时传入的方法参数
  constructor(executor) {
    try {
      // 直接执行,并将修改状态的实例方法
      executor(this.resolve, this.reject);
    } catch (err) {
      // 如果在执行函数时遇到错误,则将状态改为失败
      this.reject(err);
    }
  }

  // promise状态
  status = PENDING;

  // 成功值
  value = undefined;
  // 失败原因
  reason = undefined;

  // 异步的成功回调缓存
  successFn = [];
  // 异步的失败回调缓存
  failureFn = [];

  // 箭头函数的目的是绑定this为当前promise实例对象
  // 最终调用时并非通过promise.resolve去调用,相当于resolve.call(undefined,value)
  // 这个语法糖相当于写在constructor函数中,并将this绑定
  // 作用为将状态更改为成功
  resolve = (value) => {
    // 只有等待状态才会往下执行
    if (this.status === PENDING) {
      this.status = FUFILLED; // 修改状态
      this.value = value; // 缓存值
      while (this.successFn.length) {
        // 如果是异步的方法这个数组便会有值,取出依次调用
        this.successFn.shift()();
      }
    }
  };
  // 将状态更改为失败,参考resolve方法
  reject = (reason) => {
    if (this.status === PENDING) {
      this.status = REJECTED;
      this.reason = reason;
      while (this.failureFn.length) {
        this.failureFn.shift()();
      }
    }
  };

  // 添加默认函数让参数变为可选项,实现.then().then()可不传参数
  then = (
    onFulfilled = (v) => v,
    onRejected = (e) => { throw e; } // 箭头函数中throw语法要求需要被{}包裹
  ) => {
    // 这个promise对象会被当做返回值,可实现then().then()的前提
    let newPromise = new CustomPromise((resolve, reject) => {
      // 这里的status是被调用then方法的promise对象的的status
      // 也就是上一个promise
      // 判断状态是否成功
      if (this.status === FUFILLED) {
        // 判断返回值是promise对象还是其他
        // 如果是其他,直接调用resolve
        // 如果是promise对象,查看promise对象返回的结果再去判断返回值,来决定调用resolve还是调用reject
        
        // 需要添加一个宏任务(setTimeout),才能获取到newPromise,因为当前代码执行时还无法访问实例
        // 所以需要同步代码异步执行
        setTimeout(() => {
          try {
            // 获取then方法成功回调的返回值
            let cbValue = onFulfilled(this.value);
            // 将要返回的promise,成功回调返回值,要返回的promise的成功和失败回调传入作为参数解析
            resolvePromis(newPromise, cbValue, resolve, reject);
          } catch (error) {
            // 如果成功回调函数和解析函数抛错则将返回一个rejected的promise
            reject(error);
          }
        }, 0);
      } else if (this.status === REJECTED) {
        // 判断状态是否失败,逻辑参考成功判断
        setTimeout(() => {
          try {
            // 和成功判断的区别是,这里调用的是失败的回调,并且传入的是失败原因
            let err = onRejected(this.reason);
            resolvePromis(newPromise, err, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      } else {
        // 进入到else就是一直pending状态,说明调用then方法时还没有修改promise的状态
        // 将成功和失败回调缓存起来,通过一个函数,将成功和失败的逻辑包装起来,状态更改后直接调用即可
        this.successFn.push(() => {
          setTimeout(() => {
            try {
              let cbValue = onFulfilled(this.value);
              resolvePromis(newPromise, cbValue, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });

        // 等待失败同理
        this.failureFn.push(() => {
          setTimeout(() => {
            try {
              let err = onRejected(this.reason);
              resolvePromis(newPromise, err, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });
    return newPromise; // 将promise作为then函数的返回值
  };

  // 后面会解析
  finally = (callback) => {
    return this.then(
      (value) => {
        return CustomPromise.resolve(callback()).then(() => value);
      },
      (reason) => {
        throw CustomPromise.resolve(callback()).then(() => {
          throw reason;
        });
      }
    );
  };
  
  // 后面会解析
  catch = (callback) => {
    return this.then(undefined, callback);
  };

  // 后面会解析
  static all(array) {
    const result = [];
    let rIndex = 0;
    return new CustomPromise((resolve, reject) => {
      const push = (index, value) => {
        result[index] = value;
        rIndex++;
        if (rIndex === array.length) resolve(result);
      };
      array.forEach((item, index) => {
        if (item instanceof CustomPromise) {
          item.then((value) => push(index, value), reject);
        } else {
          push(index, item);
        }
      });
    });
  }

  // 后面会解析
  static race(array) {
    return new CustomPromise((resolve, reject) => {
      array.forEach((item) => {
        if (item instanceof CustomPromise) {
          item.then(resolve, reject);
        } else {
          resolve(item);
        }
      });
    });
  }

  // 后面会解析
  static resolve(value) {
    if (value instanceof CustomPromise) return value;
    return new CustomPromise((resolve) => resolve(value));
  }

  // 后面会解析
  static reject(value) {
    if (value instanceof CustomPromise) return value;
    return new CustomPromise((_, reject) => reject(value));
  }
}

// 解析返回值类型并执行回调
const resolvePromis = (newPromise, value, resolve, reject) => {
  // 如果成功/失败回调的返回值就是then返回的promise,将抛出错误
  if (newPromise === value) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")
    );
  }
  if (value instanceof CustomPromise) {
    // 如果是promise,则将回调透传,让传入的promise去处理即可
    value.then(resolve, reject);
  } else {
    // 非promise则直接将状态改为成功
    resolve(value);
  }
};
复制代码

核心功能已经实现的差不多,来验证下代码是否可行,将开头中模拟AjaxPromise改为手写的Promise

const promise = new CustomPromise((resolve,reject)=>{
  // 模拟Ajax请求
  setTimeout(()=>{
    resolve([{name:"jack"}]);
  },2000);
});

promise.then((res)=>{
  console.log(res);
},(err)=>{
  console.log(err);
});
复制代码

可以看到状态已被改为成功,并且回调函数中也能能到并且输出了结果数组

试试失败的案例

const post = new CustomPromise((resolve,reject)=>{
  // 模拟Ajax请求
  setTimeout(()=>{  
    reject("failure")
  },2000);
});

post.then((res)=>{
  console.log(res);
},(err)=>{
  console.log(err);
});
复制代码

同样的也成功的把状态改为失败,并且输出的错误原因

异步操作都成功,那么同步代码就不演示了,因为实现的代码更简单:)

接着测试下在then方法中将then方法返回的promise作为resolve返回值的情况

const p1 = new CustomPromise((resolve, reject) => {
  resolve("进入成功回调");
});
const p2 = p1.then((res) => {
  console.log(res); // 会输出“进入成功回调”字符串
  return p2; // 将then方法的返回值返回,由于在resolvePromise中做了判断,会执行reject并传入一个错误
});

p2.then(
  () => {},
  (err) => console.log(err.message)
  // 由于p2的状态是reject,所以失败的回调函数会执行
);
复制代码

结果也符合预期,拿到的预先定义好的错误

实现then方法参数实现可选,不传值亦可链式调用并将值往后传

目前的代码如果直接调用x.then().then()会直接报错,因为默认把回调当成函数并用()操作符去调用

只需要在then方法的参数给俩默认参数即可,在成功回调拿到传入的值并再次return,而失败回调则需要手动抛出失败原因,这里使用了默认参数和箭头函数,语法上throw需要被{}代码块包裹,要测试报错将默认参数的函数删掉即可

// 省略其他代码
class CustomPromise{
  then = (onFulfilled = v => v,onRejected = err => { throw err })=>{
    // 省略
  }
}
复制代码

实现resolve,reject,finally,catch,all,race

resolve的实现

因为在调用时是使用Promise.resolve()的形式,所以resolve是一个静态方法,不需要实例化即可通过类名去调用

实现原理也很简单,只需要判断传入的值类型,如果是Promise类型,则将promise原封不动的返回接口,其他值则将其封装成一个Promise对象再返回

// 省略其他代码
class CustomPromise{
  static resolve(value){
    // 是promise就直接返回啥也不干
    if(value instanceof CustomPromise) return value;
    return new CustomPromise(resolve => resolve(value));
  }
}
复制代码

调用时CustomPromise.resolve

// 传入非promise
CustomPromise.resolve(1).then((res) => {
  console.log(res);
  // 会输出1
});

// 传入Promise
CustomPromise.resolve(new CustomPromise((resolve) => resolve(123))).then(
  (res) => {
    console.log(res);
    // 输出promise的成功回调的return值
  }
);
复制代码

结果如下,传入非promise的输出

传入promise的输出

reject的实现

reject的实现原理和resolve类似,却别只是在于要将返回的Promise的状态改为rejected,并将reason传入

// 省略其他代码
class CustomPromise{
  static reject(reason){
    if(reason instanceof CustomPromise) return reason;
    // _是形参resolve的引用,用不到所以单纯是个占位符
    return new CustomPromise((_, reject) => reject(reason));
  }
}
复制代码

调用CustomPromise.reject

// 传入非promise
CustomPromise.reject(new Error("错了")).then(null, (reason) =>
  console.log(reason)
);

// 传入Promise
CustomPromise.resolve(
  new CustomPromise((_, reject) => reject(new Error("我错了")))
).then(null, (reason) => console.log(reason.message));
复制代码

输出的结果符合预期,都执行了then方法传入的失败回调

finally的实现

该方法是实例方法,接受一个函数作为参数,不管前面的promise状态如何,最终都会调用传入的方法,并返回一个promise,状态就是上一个promise的状态

  1. 由于方法会返回一个Promise,可以调用this.then方法,在成功和失败的回调方法中,均调用finally的传入函数
  2. 而由于回调函数可能返回promise,那么就需要等待该promise有结果时,再走下面的逻辑,利用静态方法resolve方法可实现
// 省略其他代码
class CustomPromise{
  finally = (callback) => {
    // return this.then是因为要返回一个promise,finally后还可以继续then操作
    return this.then((value) => {
      // 成功回调会执行回调函数,将球传给静态方法resolve,按照上面的实现,传入promise会原封不动的返回,也就实现了等待的功能
      return CustomPromise.resolve(callback()).then(() => value);
    },(reason)=>{
      // 失败回调也会执行回调函数,其他同理,只不过将reason手动的抛出
      // 由于成功的回调函数会抛出错误,所以后面的then能获取到错误,也就是finally().then()的状态还是失败
      return CustomPromise.resolve(callback()).then(() => { throw reason });
    })
  }
}
复制代码

测试finally方法

new CustomPromise((resolve, reject) => {
  resolve();
})
  .finally(() => {
    console.log("成功会调用");
  })
  .then(
    () => {
      console.log("finally后的resolve会调用");
    },
    () => {
      console.log("执行了就不对");
    }
  );

setTimeout(() => {
  new CustomPromise((resolve, reject) => {
    reject();
  })
    .finally(() => {
      console.log("-----------------");
      console.log("失败也会调用");
    })
    .then(
      () => {
        console.log("执行了就不对");
      },
      () => {
        console.log("finally后的reject会调用");
      }
    );
}, 1000);
复制代码

image.png

catch方法的实现

该方法是实例方法,捕获到Promise链中的错误,再返回一个Promise对象

  1. 实现的思路其实很简单,本质上也是个调用then,但是忽略了resolve回调,只传入reject回调
  2. 也就是上个promise是失败的状态才会执行
// 省略其他代码
class CustomPromise{
  catch = (callback) => {
    return this.then(null,callback)
  }
}
复制代码

测试catch方法

new CustomPromise((resolve, reject) => {
  resolve();
}).catch((err)=>{
    console.log("这句话不会执行");
})

new CustomPromise((resolve, reject) => {
  reject(new Error("故意抛出的错"));
}).catch((err)=>{
    console.log(err);
})
复制代码

测试结果为只有reject状态的promise才会执行回调函数

image.png

all方法的实现

all方法是类的静态方法,允许依照异步代码的顺序,得到异步代码的执行结果,方法接受一个数组,数组的内容可以是任意值,返回值是Promise对象

  1. 如果all方法中全部promise对象都成功,返回的promise状态为成功
  2. 如果有一个promise对象失败,返回的promise状态为失败
// 省略其他代码
class CustomPromise(){
  static all = (arr) => {
    const result = []; // 成功返回的结果集
    let rIndex = 0; // 用于判断是否全部promise都为成功
    return new Promise((resolve,reject) => {
    	const addValue = (index,value) => {
      	result[index] = value;
        rIndex++;
        // 这一步很关键,因为promise的执行时机不能确定,所以只有当调用addValue时rIndex+1后,则视为有一个结果成功
        // 当rIndex的大小与入参数组的长度相当,便相当于成功
        if(rIndex === arr.length) resolve(result)
      }
      arr.forEach((item,index) => {
        // 如果某一项是promise对象,则在该promise对象成功的回调中再添加结果到结果集中
        // 如果不是promise直接添加到结果集中
        // 如果某个promise失败了,则调用当前要返回promise的reject
      	if(item instanceof CustomPromise){
          item.then((value) => addValue(index,value), reject);
        }else{
          addValue(index,item);
        }
      })
    })
  }
}
复制代码

接下来测试下结果

// 方便测试,定义两个返回promise的函数
function testFulfilledPromise(delay) {
  return new CustomPromise((resolve, reject) => {
    setTimeout(() => {
      resolve(123);
    }, delay);
  });
}

function testRejectedPromise(delay) {
  return new CustomPromise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error("err"));
    }, delay);
  });
}

CustomPromise.all([
  1,
  2,
  testFulfilledPromise(2000),
  testFulfilledPromise(4000),
  4,
  5,
]).then(
  (res) => {
    console.log("按顺序拿到的结果集");
    console.log(res);
  },
  (err) => {
    console.log(err);
  }
);
复制代码

再4秒后,成功的拿到了返回结果集,4秒后执行了成功的回调

测试数组中有失败promise的情况
CustomPromise.all([
  1,
  2,
  testFulfilledPromise(2000),
  testRejectedPromise(4000),
  4,
  5,
]).then(
  (res) => {
    console.log("按顺序拿到的结果集");
    console.log(res);
  },
  (err) => {
    console.log("rejected:")
    console.log(err);
  }
);
复制代码

预期也是满足执行了失败回调,在4秒后执行了失败的回调

race方法

该方法与all方法的区别是,只要有任意一个promise返回了成功或者失败状态,便以这个promise为返回的状态结果。race有赛跑的意思,谁先跑到终点,不管这个人状态怎么样,他都是冠军了,就用他的状态当返回结果

  1. 接收参数与all方法相同
  2. 如果传入的数组中有非promise对象,将起包装成promise再返回,状态为成功
  3. 如果是promise对象,在promise状态更改后,调用要返回的promise对象的回调(相当于数组中某个promise更改状态后,当前要returnpromise也跟着更改状态)
  4. 测试函数延用all实例中的
// 省略其他代码
class CustomPromise(){
  static race(arr) {
    return new CustomPromise((resolve, reject) => {
      arr.forEach((item) => {
        if (item instanceof CustomPromise) {
          item.then(resolve, reject);
        } else {
          resolve(item);
        }
      });
    });
  }
}
复制代码
测试成功Promise先于失败Promise
console.log(new Date())
CustomPromise.race([
  testFulfilledPromise(2000),
  testRejectedPromise(3000),
]).then(
  (res) => {
    console.log(new Date())
    console.log("拿到更改状态最快的结果");
    console.log(res);
  },
  (err) => {
    console.log(err);
  }
);
复制代码

测试结果为以两秒后返回的promise为结果,并且状态为成功

image.png

测试失败Promise先于成功Promise
console.log(new Date())
CustomPromise.race([
  testFulfilledPromise(2000),
  testRejectedPromise(1000),
]).then(
  (res) => {
    console.log(new Date())
    console.log("拿到更改状态最快的结果");
    console.log(res);
  },
  (err) => {
    console.log(new Date())
    console.log("拿到更改状态最快的结果");
    console.log(err);
  }
);
复制代码

测试结果为以一秒后返回的promise为结果,并且状态为失败

image.png

Promise的主要功能都实现了一遍,通过这个简易版Promise的实现,不是真的要写出一个Promise,而是写完后再看ES6提供的原生Promise能有更深的理解:)

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享