javascript 模拟实现 Promise

Promise解决了什么问题?

这篇文章假定您已经有了些许的Promise使用经验。关于Promise解决的问题,大多数文章值提出了“回调地狱”这一观点

这确实是Promise解决的一大问题,但是,关于Promise的亮点,还包括但不限于以下几方面:

  • 解决回调函数的控制反转导致的信任问题:

    Promise提供了针对于第三方在调用回调函数时关于错误的调用时机,错误的调用次数等方面的解决方案,具体实现可以参考《你不知道的Javascript》中卷 第二部分–第三章–3.3部分–Promise信任问题 相关内容。

  • 指定回调函数的方式更加灵活:

    之前的异步方式必须要在启动之前指定回调函数,这是因为以前的异步方式无法保存异步状态,导致会在得到结果后就立马进入回调函数中进行处理,而Promise因为可以保存异步的执行状态和返回值,所以无论是在异步启动前,还是启动后,甚至在已经得到结果后再指定回调函数都是被允许的。

宏队列与微队列

Event Loop(事件循环)是JavaScript的执行模型,不过它并不是我们这篇文章要探讨的,我们要引用其中两个很重要的两个概念:Macrotask(宏队列)、Microtask(微队列),理解了这两个概念,可以解决我们在编写Promise中的一些疑惑。

首先先来看三段代码:

// 第一段代码
setTimeout(() => {
  console.log(1); // 我后输出
}, 0)

console.log(2); // 我先输出
// 第二段代码
Promise.resolve(1).then(resolved => console.log(resolved)); // 我后输出

console.log(2); // 我先输出
// 第三段代码
setTimeout(() => {
  console.log(1); // 我第二个输出
  Promise.resolve(3).then((e) => console.log(e)); // 我第3个输出
}, 0);

setTimeout(() => {
  console.log(2); // 我最后输出
}, 0);
console.log(4); // 我先输出

从前面两段代码中,我们不难看出,setTimeoutPromise.prototype.then都会异步执行其中的代码片段,但是第三段代码中,在最后加入异步队列的Promise.prototype.then却排在了第二个setTimeout`前面。

我们直接说结论,原因是在JavaScript的执行模型中,异步队列分为宏队列和微队列两种,其中setTimeout属于宏队列,Promise.prototype.then属于微队列。二者的共同点是都会等待JS执行栈全部pop空后才执行。

优先级也就是说
调用栈 > 微任务 > 宏任务

它们的区别是宏队列一次只会弹出一个回调函数执行,并且每一个宏队列函数执行完毕后,都会检测当前微队列中有无待执行函数,如果有会一次性将微队列中的全部待执行函数执行完毕。

带入到第三段代码中,Promise之所以会先于第二个定时器就是因为在第一个定时器执行完毕后,检测到微队列中包含一个Promise待执行函数,所以会将微队列的函数执行完后,再返回宏队列执行接下来的代码。

开始实现Promise

我们先看一段基本的Promise使用代码:

let asyncCode = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  });
});

asyncCode.then(
  (resolved) => {
    console.log(resolved); // 1
  },
  (rejected) => {
    console.log(rejected);
  }
);

我们先列出从上面的代码中就可以看出的规范:

  1. Promise是一个构造函数,构造一个Promise实例需要传入一个回调函数;
  2. 传入的参数函数中包含两个参数,并且这两个参数也是函数;
  3. 构造出的Promise实例身上包含then方法;
  4. then方法中需要传入两个函数类型的参数,两个函数各有一个参数。

以上面的规则,我们就可以开始构建:

(function (window) {
  function MyPromise(executor) {
    function resolve(value) {
        
    }
     
    function reject(reason) {
        
    }
    
    executor(resolve, reject)
  }

  MyPromise.prototype.then = function (onResolved, onRejected) {

  }
})(window)

术语

  • 解决(fulfill):指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用 fulfill 来表示解决,但在后世的 promise 实现多以 resolve 来指代之。
  • 拒绝(reject):指一个 promise 失败时进行的一系列操作。
  • 终值(eventual value):所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。
  • 据因(reason):也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值。

Promise构造函数编写

根据规范,一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)执行态(Fulfilled)拒绝态(Rejected)

等待态(Pending)

处于等待态时,promise 需满足以下条件:

  • 可以迁移至执行态或拒绝态

执行态(Fulfilled)

处于执行态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的终值

拒绝态(Rejected)

处于拒绝态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的拒因

这里的不可变指的是恒等(即可用 === 判断相等),而不是意味着更深层次的不可变(盖指当 value 或 reason 不是基本值时,只要求其引用地址相等,但属性值可被更改)。
另外,ES2015中并没有选择”Fulfilled“作为执行态,而是选择与”rejected“相对应的“resolved”,我们的实现也遵从ES2015的实现。

function MyPromise(executor) {
    // Promise当前的状态
    this.state = "pending";
    
    // 由于Promise只会有一种状态,所以我们利用一个属性来存储返回的终值或拒因
    this.data = undefined;
    
    // 使用数组是因为同一个Promise实例可能被多次调用then方法
    // 每个元素的结构:{onResolved() {}, onRejected() {}}
    this.callbacks = [];
    
    
    // ... other code
}

接下来我们再来实现需要传入executor中的resolvereject方法,它们的逻辑包括:

  1. 由于Promise只有一次更改状态的机会,所以只要当前的state不为"pending",直接return;
  2. resolve需要保存本次异步的终值,reject需要保存本次异步的拒因;
  3. 由于浏览器并没有开放将代码push到微队列的接口,所以我们借用官方的Promise.prototype.then方法来实现合适的回调函数的调用时机
function MyPromise(executor) {
  // ... other code 
   
  function resolve(value) {
    if (this.state !== "pending") return;

    // 将状态改为resolved
    this.state = "resolved";

    // 保存value数据
    this.data = value;

    // 如果有待执行的回调函数,依次添加入异步队列中(此处用宏队列模拟微队列)
    if (this.callbacks.length > 0) {
      let _this = this;
      Promise.resolve(null).then((e) => {
        _this.callbacks.forEach((callbacksObj) => {
          callbacksObj.onResolved(value);
        });
      });
    }
  }

  function reject(reason) {
    if (this.state !== "pending") return;

    // 将状态改为rejected
    this.state = "rejected";

    // 保存reason数据
    this.data = reason;

    // 如果有待执行的回调函数,依次添加入异步队列中(此处用宏队列模拟微队列)
    if (this.callbacks.length > 0) {
      // setTimeout(() => {
      //   this.callbacks.forEach((callbacksObj) => {
      //     callbacksObj.onRejected(reason);
      //   });
      // });

      // 使用Promise.prototype.then来模拟为微队列效果
      Promise.resolve(null).then((e) => {
        this.callbacks.forEach((callbacksObj) => {
          callbacksObj.onRejected(reason);
        });
      });
    }
  }
}

最后因为Promise的构建是同步执行的,所以我们在构造函数中立即执行传进来的构建器:

// 立即执行executor
try {
  executor(resolve.bind(this), reject.bind(this));
} catch (error) {
  reject(error); // 如果执行器抛出异常,Promise为失败状态
}

构造函数的最终代码为

(function (window) {
  function MyPromise(executor) {
    // Promise当前的状态
    this.state = "pending";

    // 由于Promise只会有一种状态,所以我们利用一个属性来存储返回的终值或拒因
    this.data = undefined;

    // 使用数组是因为同一个Promise实例可能被多次调用then方法
    // 每个元素的结构:{onResolved() {}, onRejected() {}}
    this.callbacks = [];

    function resolve(value) {
      if (this.state !== "pending") return;

      // 将状态改为resolved
      this.state = "resolved";

      // 保存value数据
      this.data = value;

      // 如果有待执行的回调函数,依次添加入异步队列中(此处用宏队列模拟微队列)
      if (this.callbacks.length > 0) {
        let _this = this;
        Promise.resolve(null).then((e) => {
          _this.callbacks.forEach((callbacksObj) => {
            callbacksObj.onResolved(value);
          });
        });
      }
    }

    function reject(reason) {
      if (this.state !== "pending") return;

      // 将状态改为rejected
      this.state = "rejected";

      // 保存reason数据
      this.data = reason;

      // 如果有待执行的回调函数,依次添加入异步队列中(此处用宏队列模拟微队列)
      if (this.callbacks.length > 0) {
        // setTimeout(() => {
        //   this.callbacks.forEach((callbacksObj) => {
        //     callbacksObj.onRejected(reason);
        //   });
        // });

        // 使用Promise.prototype.then来模拟为微队列效果
        Promise.resolve(null).then((e) => {
          this.callbacks.forEach((callbacksObj) => {
            callbacksObj.onRejected(reason);
          });
        });
      }
    }

    // 立即执行executor
    try {
      executor(resolve.bind(this), reject.bind(this));
    } catch (error) {
      reject(error); // 如果执行器抛出异常,Promise为失败状态
    }
  }
  
  // 用于测试,暂时假定Promise的状态为pending
  MyPromise.prototype.then = function (onResolved, onRejected) {
    this.callbacks.push({ onResolved, onRejected });
  };

  window.MyPromise = MyPromise;
})(window);

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以帮你模拟实现一个 Promise.finally 方法。这个方法会在 Promise 完成或被拒绝后都会执行一段回调函数。 首先,我们需要创建一个 Promise 类,它包含一个状态和相关的方法: ```javascript class MyPromise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.error = undefined; this.finallyCallback = undefined; const resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; if (this.finallyCallback) { this.finallyCallback(); } } }; const reject = error => { if (this.state === 'pending') { this.state = 'rejected'; this.error = error; if (this.finallyCallback) { this.finallyCallback(); } } }; try { executor(resolve, reject); } catch (error) { reject(error); } } finally(callback) { if (this.state !== 'pending') { callback(); } else { this.finallyCallback = callback; } } } ``` 在这个实现中,我们使用了一个 `finallyCallback` 属性来保存传入的回调函数。当 Promise 的状态变为 `fulfilled` 或 `rejected` 时,如果有回调函数存在,就会执行它。 接下来,我们可以使用这个自定义的 Promise 类来测试实现的 `finally` 方法: ```javascript const promise = new MyPromise((resolve, reject) => { setTimeout(() => { resolve('Success'); }, 2000); }); promise .then(value => { console.log(value); }) .finally(() => { console.log('Finally callback executed'); }); ``` 这段代码创建了一个 Promise,经过2秒后状态变为 `fulfilled`,然后会执行 `then` 方法中的回调函数,并在最后执行 `finally` 方法中的回调函数。 希望这个简单的实现能帮到你!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值