當前位置: 首頁 / 技術分享 / 正文
手封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(北京)

    開班盛況
在線咨詢
試聽
入學教程
立即報名

Copyright 2011-2023 北京千鋒互聯科技有限公司 .All Right 京ICP備12003911號-5 京公網安備 11010802035720號

美女裸体黄网站18禁免费_美女裸体黄网站啪啪18禁_美女裸体黄污18禁网站免费下载