当前位置: 首页 / 技术干货 / 正文
手封MyPromise

2022-12-02

   mypromise

  引子

  面试时如果被问到“如果没有Promise,能不能自己实现一个”,就是在深度考察对异步的理解了;

  如果你的回答是“能,我封过”,则必然令人刮目相看~

  事实上,作为ES6新增的API,即使没有,用户也完全可以基于想要的效果自己去实现一个;

  这里给到大家我个人的实现:MyPromise;

  源码地址

  实现思路

  将then/catch/finally中入参的回调函数按顺序收集到一个队列中;

  亲自实现一下resolve和reject方法;

  一旦实例resolve就将队列头部的catch回调都弹出,一旦实例reject就将队列头部的then回调都弹出,然后拿取队列头部的回调函数实行起来;

  每次回调的执行结果即返回值需要继续resolve或reject一下,然后继续递归执行上述过程,直到回调队列为空;

  接收任务执行器

  当我们new出一个MyPromise,并内置一个任务执行器函数的时候,需要在MyPromise的构造器中接收任务执行器函数,并同步地、立即地将其执行起来;

  要特别注意:任务执行器函数是同步地跑在主线程的,而不属于异步回调;

  test.js

  new MyPromise(

  /* 任务执行器:2秒后随机履约或毁约 */

  (resolve, reject) => {

  setTimeout(() => {

  Math.random() > 0.5 ? resolve("data0") : reject(new Error("一张好人卡"));

  }, 2000);

  }

  )

  MyPromise.js

  class MyPromise {

  constructor(executor) {

  /* 接收任务执行器并立即调用(同步调用) */

  this.executor = executor;

  this.executor();

  }

  }

  定义resolve与reject

  任务执行器函数会在稍后履约(resolve)或毁约(reject)

  因此要告诉MyPromise履约或毁约时具体做什么

  这里我们先做一下简单的打印

  MyPromise.js

  class MyPromise {

  constructor(executor) {

  /* 接收任务执行器并立即调用(同步调用) */

  /* 绑定执行器函数中的resolve与reject */

  this.executor = executor.bind(

  this, //当前promise实例

  

  //当前promise实例执行resolve时,this不变

  MyPromise.doResolve.bind(this),

  

  //当前promise实例执行reject时,this不变

  MyPromise.doReject.bind(this)

  );

  this.executor();

  }

  

  /* promise实例执行resolve */

  static doResolve(data) {

  console.log("doResolve", data);

  }

  

  /* promise实例执行reject */

  static doReject(err) {

  console.log("doReject", err);

  }

  }

  

  module.exports = MyPromise;

  明确回调期望

  明确一下异步任务履约/毁约后我们希望做什么

  我们希望一旦异步任务resolve就执行then中的回调

  我们希望一旦异步任务reject就执行catch中的回调

  我们希望异步任务无论成功还是失败,最终都执行finally中的回调

  期望如下:test.js

  const MyPromise = require("./MyPromiseX")

  

  new MyPromise(

  /* 任务执行器:2秒后随机履约或毁约 */

  (resolve, reject) => {

  setTimeout(() => {

  Math.random() > 0.1 ? resolve("data0") : reject(new Error("一张好人卡"));

  }, 2000);

  }

  )

  .then(

  data => {

  console.log("then1:data=",data)

  return "data from then1"

  // throw new Error("err from then1")

  }

  )

  .catch(

  err => {

  console.log("catch1:err=",err)

  return "data from catch1"

  }

  )

  .finally(

  ()=>console.log("finally:game over!")

  )

  收集回调队列

  每一个then中有一个成功回调

  每一个catch中有一个失败回调

  finally中有一个最终回调

  我们先按顺序将这些回调收集在一个队列中

  代码如下:MyPromise.js

  class MyPromise {

  constructor(executor) {

  

  // 回调队列

  this.callbacks = [];

  

  /* 接收任务执行器并立即调用(同步调用) */

  // 绑定执行器函数中的resolve与reject

  this.executor = executor.bind(

  this, //当前promise实例

  

  //当前promise实例执行resolve时,this不变

  MyPromise.doResolve.bind(this),

  

  //当前promise实例执行reject时,this不变

  MyPromise.doReject.bind(this)

  );

  

  // 立即调用任务执行器

  this.executor();

  }

  

  /* promise实例执行resolve */

  static doResolve(data) {

  console.log("doResolve", data);

  }

  

  /* promise实例执行reject */

  static doReject(err) {

  console.log("doReject", err);

  }

  

  /* 收集成功回调到队列 */

  then(onData) {

  // 将来一旦Promise毁约 回调队列头部的所有then回调都要弹出作废

  this.callbacks.push({ type: "then", callback: onData });

  return this;

  }

  

  /* 收集失败回调到队列 */

  catch(onErr) {

  // 将来一旦Promise履约 回调队列头部的所有catch回调都要弹出作废

  this.callbacks.push({ type: "catch", callback: onErr });

  return this;

  }

  

  /* 收集终点回调到队列(此处假设只有一个终点回调) */

  finally(onFinish) {

  this.callbacks.push({ type: "finally", callback: onFinish });

  return this;

  }

  }

  

  module.exports = MyPromise;

  此时在测试脚本test.js的末尾打印一下回调队列的话,你会看到如下输出:

  [

  { type: 'then', callback: [Function (anonymous)] },

  { type: 'catch', callback: [Function (anonymous)] },

  { type: 'finally', callback: [Function (anonymous)] }

  ]

  如果来上一堆乱序的then/catch/finally的话,则你会看到如下输出:

  [

  { type: 'then', callback: [Function (anonymous)] },

  { type: 'then', callback: [Function (anonymous)] },

  { type: 'then', callback: [Function (anonymous)] },

  { type: 'then', callback: [Function (anonymous)] },

  { type: 'catch', callback: [Function (anonymous)] },

  { type: 'catch', callback: [Function (anonymous)] },

  { type: 'then', callback: [Function (anonymous)] },

  { type: 'finally', callback: [Function (anonymous)] }

  ]

  连环作案(履约或毁约)

  让我们在每次成功或失败后,都随机地继续履约或毁约起来!

  test.js

  const MyPromise = require("./MyPromiseX")

  

  const p = new MyPromise(

  /* 任务执行器:2秒后随机履约或毁约 */

  (resolve, reject) => {

  setTimeout(() => {

  Math.random() > 0.5 ? resolve("data0") : reject(new Error("一张好人卡"));

  }, 2000);

  }

  )

  .then(

  data => {

  console.log("then1:data=",data)

  

  /* 继续履约或毁约! */

  return "data from then1"

  // throw new Error("err from then1")

  }

  )

  .then(

  data => {

  console.log("then2:data=",data)

  

  /* 继续履约或毁约! */

  return MyPromise.resolve("data from then2")

  // return MyPromise.reject("err from then2")

  }

  )

  .then(

  data => {

  console.log("then3:data=",data)

  

  /* 继续履约或毁约! */

  return new MyPromise(

  (resolve,reject) => setTimeout(() => {

  resolve("data from then3")

  // reject("err from then3")

  }, 2000)

  )

  }

  )

  .then(

  /* 这里本质继续履约了一个undefined */

  data => console.log("then4:data=",data)

  )

  .catch(

  err => {

  console.log("catch1:err=",err)

  

  /* 继续履约 */

  return "data from catch1"

  }

  )

  .catch(

  /* 这里本质继续履约了一个undefined */

  err => console.log("catch2:err=",err)

  )

  .then(

  /* 这里本质继续履约了一个undefined */

  data => console.log("then5:data=",data)

  )

  .finally(

  /* 这里本质继续履约了一个undefined */

  ()=>console.log("finally:game over!")

  )

  

  // 打印一下MyPromise对象的回调队列

  console.log("p.callbacks",p.callbacks);

  所谓MyPromise.resolve(data)或MyPromise.reject(err)无非也就是newPromise(executor)的一个语法糖而已,实现如下:

  MyPromise.js片段

  /* 语法糖:创建一个立即resolve的Promise对象 */

  static resolve(data) {

  return new MyPromise((resolve) => resolve(data));

  }

  

  /* 语法糖:创建一个立即reject的Promise对象 */

  static reject(err) {

  return new MyPromise((resolve, reject) => reject(err));

  }

  准备执行连环回调

  我们无非想要如下效果:

  只要前面的环节履约了:在回调队列中找出下一个type为then的回调执行起来;

  只要前面的环节毁约了:在回调队列中找出下一个type为catch的回调执行起来;

  找下一个then回调前,所有位于它前面的catch都驱逐先;

  找下一个catch回调前,所有位于它前面的then都驱逐先;

  我们来撸码实现之!

  定义MyPromise状态

  class MyPromise {

  /* Promise状态定义 */

  static STATUS_PENDING = 0; // 挂起态

  static STATUS_FULFILLED = 1; // 履约态

  static STATUS_REJECTED = 2; // 毁约态

  ...

  }

  定义回调入参

  constructor(executor) {

  /* 回调队列 + 回调入参 (JS单线程模型=每次只能有一个回调被执行) */

  this.callbacks = [];

  

  // 定义给回调传的入参,无论是数据还是错误,我们统称为cbArg

  this.cbArg = null;

  ...

  }

  定义驱逐器

  成功时:从回调队列头部把所有的catch驱逐

  失败时:从回调队列头部把所有的then驱逐

  /*

  成功时:从回调队列头部把所有的catch驱逐

  失败时:从回调队列头部把所有的then驱逐

  */

  shiftCallbacksWithType(type) {

  /* 只要回调队列中还有回调,就把队列头部type为指定类型的回调弹出去 */

  while (this.callbacks.length && this.callbacks[0].type === type) {

  // 驱逐一个回调函数

  this.callbacks.shift();

  }

  }

  执行连环回调

  当前任履约时

  状态设置为履约态

  将回调入参设置为需要履约的数据

  找下一个then里的回调执行起来

  /* promise实例执行resolve */

  static doResolve(data) {

  /* 设置状态为履约态 + 设置回调时的入参 + 拉起下一次回调 */

  this.status = MyPromise.STATUS_FULFILLED;

  

  // 回调入参为数据

  this.cbArg = data;

  

  // 拉起下一次then回调

  setTimeout(() => {

  this.next();

  });

  }

  当前任毁约时

  状态设置为毁约态

  将回调入参设置为毁约的原因

  找下一个catch里的回调执行起来

  /* promise实例执行reject */

  static doReject(err) {

  /* 设置状态为毁约态 + 设置回调时的错误 + 拉起下一次回调 */

  this.status = MyPromise.STATUS_REJECTED;

  

  // 回调入参为错误

  this.cbArg = err;

  

  // 拉起下一次catch回调

  setTimeout(() => {

  this.next();

  });

  }

  执行下一次回调

  整个MyPromise最核心的逻辑来了

  只要当前MyPromise为履约态,就驱逐队列头部所有的catch回调先

  只要当前MyPromise为毁约态,就驱逐队列头部所有的then回调先

  拿取队列头部的那个回调执行起来

  next() {

  /* 确定该回调哪一个callback */

  if (this.status === MyPromise.STATUS_FULFILLED) {

  // 履约时:弹光队列头部的catch回调

  this.shiftCallbacksWithType("catch");

  }

  

  if (this.status === MyPromise.STATUS_REJECTED) {

  // 毁约时:弹光队列头部的then回调

  this.shiftCallbacksWithType("then");

  }

  

  /* 如果回调队列已空,则直接结束程序 */

  if (!this.callbacks.length) {

  console.log("回调队列已空");

  return;

  }

  

  /* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */

  let { callback } = this.callbacks.shift();

  // 执行回调并拿到其结果(value或promise对象)

  let value = callback(this.cbArg);

  }

  当回调返回value

  test.js片段

  .then(

  data => {

  console.log("then1:data=",data)

  

  /* 继续履约或毁约! */

  return "data from then1"

  // throw new Error("err from then1")

  }

  )

  当回调返回一个新的(非MyPromise对象)的普通value时,我们继续向后resolve它

  程序会继续doResolve,doResolve内部会继续拉起下一次next

  下一次next找出的回调还有新的返回值

  递归了解一下!归了解一下!了解一下!解一下!一下!下!

  next() {

  ...

  // 执行回调并拿到其结果(value或promise对象)

  let value = callback(this.cbArg);

  

  /* 如果回调函数返回一个value 继续向下resolve(value) */

  if (!(value instanceof MyPromise)) {

  MyPromise.doResolve.call(this, value);

  }

  ...

  }

  当回调抛出错误时

  test.js片段

  .then(

  data => {

  console.log("then1:data=",data)

  

  /* 继续履约或毁约! */

  // return "data from then1"

  throw new Error("err from then1")

  }

  )

  try-catch起来,把错误信息reject一下

  程序会继续doReject,doReject内部会继续拉起下一次next

  下一次next找出的回调还有新的返回值

  递归了解一下!归了解一下!了解一下!解一下!一下!下!

  next() {

  ...

  /* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */

  let { callback } = this.callbacks.shift();

  

  /* 防止回调函数中throw错误 */

  try {

  // 执行回调并拿到其结果(value或promise对象)

  let value = callback(this.cbArg);

  

  /* 如果回调函数返回一个value 继续向下resolve(value) */

  if (!(value instanceof MyPromise)) {

  MyPromise.doResolve.call(this, value);

  }else {

  ...

  }

  } catch (err) {

  // 回调函数抛出错误时相当于reject(err)

  MyPromise.doReject.call(this, err);

  }

  }

  当回调返回一个新的MP对象时

  .then(

  data => {

  console.log("then2:data=",data)

  

  /* 继续履约或毁约! */

  return MyPromise.resolve("data from then2")

  // return MyPromise.reject("err from then2")

  }

  )

  .then(

  data => {

  console.log("then3:data=",data)

  

  /* 继续履约或毁约! */

  return new MyPromise(

  (resolve,reject) => setTimeout(() => {

  resolve("data from then3")

  // reject("err from then3")

  }, 2000)

  )

  }

  )

  首先,MP的构造器一定会被调用!

  构造器里有this.executor(),this.executor()里有resolve(data)或reject(err),也就是doResolve(data)或doReject(err)

  不断递归next()找下一次回调的过程会自发地跑起

  这个该死的MP对象可能自带一个回调队列

  我们把当前MP对象剩余的回调队列过户给它即可

  next() {

  ...

  /* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */

  let { callback } = this.callbacks.shift();

  

  /* 防止回调函数中throw错误 */

  try {

  // 执行回调并拿到其结果(value或promise对象)

  let value = callback(this.cbArg);

  

  /* 如果回调函数返回一个value 继续向下resolve(value) */

  if (!(value instanceof MyPromise)) {

  ...

  }

  else {

  // 如果回调函数返回一个Promise对象

  // 将后续所有callback过户给新的Promise对象

  value.callbacks = value.callbacks.concat(this.callbacks);

  }

  } catch (err) {...}

  }

  完整的next函数

  递归inside!

  /*

  从回调队列里拉取下一个适当的callback并回调之

  这是MyPromise的核心代码:递归inside!

  */

  next() {

  /* 确定该回调哪一个callback */

  if (this.status === MyPromise.STATUS_FULFILLED) {

  // 履约时:弹光队列头部的catch回调

  this.shiftCallbacksWithType("catch");

  }

  

  if (this.status === MyPromise.STATUS_REJECTED) {

  // 毁约时:弹光队列头部的then回调

  this.shiftCallbacksWithType("then");

  }

  

  /* 如果回调队列已空,则直接结束程序 */

  if (!this.callbacks.length) {

  console.log("回调队列已空");

  return;

  }

  

  /* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */

  let { callback } = this.callbacks.shift();

  

  /* 防止回调函数中throw错误 */

  try {

  // 执行回调并拿到其结果(value或promise对象)

  let value = callback(this.cbArg);

  

  /* 如果回调函数返回一个value 继续向下resolve(value) */

  if (!(value instanceof MyPromise)) {

  MyPromise.doResolve.call(this, value);

  }

  else {

  // 如果回调函数返回一个Promise对象

  // 将后续所有callback过户给新的Promise对象

  value.callbacks = value.callbacks.concat(this.callbacks);

  }

  } catch (err) {

  // 回调函数抛出错误时相当于reject(err)

  MyPromise.doReject.call(this, err);

  }

  }

  完整代码

  MyPromise.js

  class MyPromise {

  /* Promise状态定义 */

  static STATUS_PENDING = 0; // 挂起态

  static STATUS_FULFILLED = 1; // 履约态

  static STATUS_REJECTED = 2; // 毁约态

  

  constructor(executor) {

  /* 回调队列 + 回调入参 (JS单线程模型=每次只能有一个回调被执行) */

  this.callbacks = [];

  

  // 定义给回调传的入参,无论是数据还是错误,我们统称为cbArg

  this.cbArg = null;

  

  /* 绑定执行器函数中的resolve与reject */

  this.executor = executor.bind(

  this, //当前promise实例

  

  //当前promise实例执行resolve时,this不变

  MyPromise.doResolve.bind(this),

  

  //当前promise实例执行reject时,this不变

  MyPromise.doReject.bind(this)

  );

  

  // 执行任务前先将Promise状态设置为pending

  this.status = MyPromise.STATUS_PENDING;

  

  // 执行任务(任务中会resolve/doResolve或reject/doReject)

  this.executor();

  }

  

  /* promise实例执行resolve */

  static doResolve(data) {

  /* 设置状态为履约态 + 设置回调时的入参 + 拉起下一次回调 */

  this.status = MyPromise.STATUS_FULFILLED;

  

  // 回调入参为数据

  this.cbArg = data;

  

  // 拉起下一次then回调

  setTimeout(() => {

  this.next();

  });

  }

  

  /* promise实例执行reject */

  static doReject(err) {

  /* 设置状态为毁约态 + 设置回调时的错误 + 拉起下一次回调 */

  this.status = MyPromise.STATUS_REJECTED;

  

  // 回调入参为错误

  this.cbArg = err;

  

  // 拉起下一次catch回调

  setTimeout(() => {

  this.next();

  });

  }

  

  /*

  成功时:从回调队列头部把所有的catch驱逐

  失败时:从回调队列头部把所有的then驱逐

  */

  shiftCallbacksWithType(type) {

  /* 只要回调队列中还有回调,就把队列头部type为指定类型的回调弹出去 */

  while (this.callbacks.length && this.callbacks[0].type === type) {

  // 驱逐一个回调函数

  this.callbacks.shift();

  }

  }

  

  /*

  从回调队列里拉取下一个适当的callback并回调之

  这是MyPromise的核心代码:递归inside!

  */

  next() {

  /* 确定该回调哪一个callback */

  if (this.status === MyPromise.STATUS_FULFILLED) {

  // 履约时:弹光队列头部的catch回调

  this.shiftCallbacksWithType("catch");

  }

  

  if (this.status === MyPromise.STATUS_REJECTED) {

  // 毁约时:弹光队列头部的then回调

  this.shiftCallbacksWithType("then");

  }

  

  /* 如果回调队列已空,则直接结束程序 */

  if (!this.callbacks.length) {

  console.log("回调队列已空");

  return;

  }

  

  /* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */

  let { callback } = this.callbacks.shift();

  

  /* 防止回调函数中throw错误 */

  try {

  // 执行回调并拿到其结果(value或promise对象)

  let value = callback(this.cbArg);

  

  /* 如果回调函数返回一个value 继续向下resolve(value) */

  if (!(value instanceof MyPromise)) {

  MyPromise.doResolve.call(this, value);

  }

  else {

  // 如果回调函数返回一个Promise对象

  // 将后续所有callback过户给新的Promise对象

  value.callbacks = value.callbacks.concat(this.callbacks);

  }

  } catch (err) {

  // 回调函数抛出错误时相当于reject(err)

  MyPromise.doReject.call(this, err);

  }

  }

  

  /* 语法糖:创建一个立即resolve的Promise对象 */

  static resolve(data) {

  return new MyPromise((resolve) => resolve(data));

  }

  

  /* 语法糖:创建一个立即reject的Promise对象 */

  static reject(err) {

  return new MyPromise((resolve, reject) => reject(err));

  }

  

  /* 收集成功回调到队列 */

  then(onData) {

  // 将来一旦Promise毁约 回调队列头部的所有then回调都要弹出作废

  this.callbacks.push({ type: "then", callback: onData });

  return this;

  }

  

  /* 收集失败回调到队列 */

  catch(onErr) {

  // 将来一旦Promise履约 回调队列头部的所有catch回调都要弹出作废

  this.callbacks.push({ type: "catch", callback: onErr });

  return this;

  }

  

  /* 收集终点回调到队列(此处假设只有一个终点回调) */

  finally(onFinish) {

  this.callbacks.push({ type: "finally", callback: onFinish });

  return this;

  }

  }

  

  module.exports = MyPromise;

  测试代码test.js

  const MyPromise = require("./MyPromise3")

  

  const p = new MyPromise(

  /* 任务执行器:2秒后随机履约或毁约 */

  (resolve, reject) => {

  setTimeout(() => {

  Math.random() > 0.1 ? resolve("data0") : reject(new Error("一张好人卡"));

  }, 2000);

  }

  )

  .then(

  data => {

  console.log("then1:data=",data)

  

  /* 继续履约或毁约! */

  return "data from then1"

  // throw new Error("err from then1")

  }

  )

  .then(

  data => {

  console.log("then2:data=",data)

  

  /* 继续履约或毁约! */

  return MyPromise.resolve("data from then2")

  // return MyPromise.reject("err from then2")

  }

  )

  .then(

  data => {

  console.log("then3:data=",data)

  

  /* 继续履约或毁约! */

  return new MyPromise(

  (resolve,reject) => setTimeout(() => {

  resolve("data from then3")

  // reject("err from then3")

  }, 2000)

  )

  }

  )

  .then(

  /* 这里本质继续履约了一个undefined */

  data => console.log("then4:data=",data)

  )

  .catch(

  err => {

  console.log("catch1:err=",err)

  

  /* 继续履约 */

  return "data from catch1"

  }

  )

  .catch(

  /* 这里本质继续履约了一个undefined */

  err => console.log("catch2:err=",err)

  )

  .then(

  /* 这里本质继续履约了一个undefined */

  data => console.log("then5:data=",data)

  )

  .finally(

  /* 这里本质继续履约了一个undefined */

  ()=>console.log("finally:game over!")

  )

  

  // 打印一下MyPromise对象的回调队列

  console.log("p.callbacks",p.callbacks);

  测试效果

  https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c47910678fd41acba1eed2ea02bbabb~tplv-k3u1fbpfcp-watermark.image?

好程序员公众号

  • · 剖析行业发展趋势
  • · 汇聚企业项目源码

好程序员开班动态

More+
  • HTML5大前端 <高端班>

    开班时间:2021-04-12(深圳)

    开班盛况

    开班时间:2021-05-17(北京)

    开班盛况
  • 大数据+人工智能 <高端班>

    开班时间:2021-03-22(杭州)

    开班盛况

    开班时间:2021-04-26(北京)

    开班盛况
  • JavaEE分布式开发 <高端班>

    开班时间:2021-05-10(北京)

    开班盛况

    开班时间:2021-02-22(北京)

    开班盛况
  • Python人工智能+数据分析 <高端班>

    开班时间:2021-07-12(北京)

    预约报名

    开班时间:2020-09-21(上海)

    开班盛况
  • 云计算开发 <高端班>

    开班时间:2021-07-12(北京)

    预约报名

    开班时间:2019-07-22(北京)

    开班盛况
IT培训IT培训
在线咨询
IT培训IT培训
试听
IT培训IT培训
入学教程
IT培训IT培训
立即报名
IT培训

Copyright 2011-2023 北京千锋互联科技有限公司 .All Right 京ICP备12003911号-5 京公网安备 11010802035720号