<strong id="8cmf8"><pre id="8cmf8"></pre></strong><progress id="8cmf8"><track id="8cmf8"></track></progress>

<button id="8cmf8"><object id="8cmf8"></object></button>

    <button id="8cmf8"><acronym id="8cmf8"></acronym></button>

  1. <button id="8cmf8"><acronym id="8cmf8"></acronym></button>

        当前位置: 首页 / 技术分享 / 正文
        手封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号

        国产三级片在线视频
        <strong id="8cmf8"><pre id="8cmf8"></pre></strong><progress id="8cmf8"><track id="8cmf8"></track></progress>

        <button id="8cmf8"><object id="8cmf8"></object></button>

          <button id="8cmf8"><acronym id="8cmf8"></acronym></button>

        1. <button id="8cmf8"><acronym id="8cmf8"></acronym></button>