详解JavaScript如何实现一个简易的Promise对象

 更新时间:2022年11月30日 16:09:47   作者:寒月十九  
Promise对象的作用将异步操作以同步操作的流程表达出来,避免层层嵌套的回调函数,而且Promise提供了统一的接口,使得控制异步操作更加容易。本文介绍了如何实现一个简单的Promise对象,需要的可以参考一下

前言

实现一个简易的Promise对象,我们首先要了解几个相关的知识点:

Promise对象的状态: pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

Promise的参数: Promise构造函数接收一个函数作为参数,函数内部有两个参数,分别是resolve和reject,这两个参数是两个函数,由JS引擎提供,不需要我们部署。reslove函数的作用是将Promise对象的状态由 'pending' 状态变为 'resolved'状态即('fulfilled'状态),方便与参数名对应,reject函数的作用是将Promise对象的状态由 'pending' 状态变为 'rejected'状态。

但是我们应该注意的是,Promise对象的状态一经改变,就不再发生改变(即pending --> resolved || pending --> rejected 其中任意一种发生改变之后,Promise对象的状态将不再发生改变)

Promise的基础结构与用法

 let p1 = new Promise((resolve, reject) => {
   resolve('成功');
   reject('失败');
   throw('报错');  //相当于reject()
 })

 console.log(p1);

让我们看看三种状态的打印的结果分别是什么吧

使用class类实现promise对象

class myPromise {
  constructor(executor) {
    this.status = 'pending'; // 变更promise的状态
    this.value = null;
    
    executor(this.resolve, this.reject); // new 一个myPromise 得到的实例对象里面有两个函数
  }

  resolve(value) {
    if (this.status !== 'pending') return;
    this.status = 'fulfilled'; // 变更promise的状态
    this.value = value;
  }

  reject(reason) {
    if (this.status !== 'pending') return
    this.status = 'rejected';
    this.value = reason;
  }
}

貌似这么写代码逻辑也说得通,那么让我们看一下实现的效果:

看到这里,不难想到我们的resolve和reject的两个方法的this指向出现了问题,我们仔细看看不难发现,这两个方法被我们作为实参放到了构造器函数,此时this的指向是指向了构造器函数,而不是我们写的myPromise这个构造函数身上,那只需要bind显示绑定一下this 的指向就可以解决了。

executor(this.resolve.bind(this), this.reject.bind(this))

并且myPromise的状态一经变更也不再改变,是不是有一点原装Promise的味道了。但是在Promise里面还有一个错误捕捉机制,只要promise里面执行的逻辑报错了,就需要走reject逻辑,将错误抛出来,那我们只需要使用try catch来实现就可以。

try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error)
    }

这样我们就实现了极简版的Promise对象,但是通常情况下我们都是使用Promise对象来处理异步的问题,说到异步,那不得不提起Promise.prototype.then()这个方法了,then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

then方法的第一个参数是`resolved状态的回调函数`,第二个参数是`rejected状态的回调函数`,它们都是可选的。

当promise的状态为'fulfilled'会执行第一个回调函数,当状态为'rejected'时执行第二个回调函数。

必须等到Promise的状态变更过一次之后,状态为'fulfilled'或者'rejected',才去执行then里面的逻辑。

.then支持链式调用,下一次.then受上一次.then执行结果的影响。

知道以上这几点,我们就可以尝试如何实现.then方法了

class myPromise {
  constructor(executor) {
    this.status = 'pending'; 
    this.value = null;
    this.onFulfilledCallbacks = []; // 用来保存成功的回调(处理异步)
    this.onRejectedCallbacks = []; // 用来保存失败的回调(处理异步)
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error)
    }
  }

  resolve(value) {
    if (this.status !== 'pending') return;
    this.status = 'fulfilled';
    this.value = value;
    // 调用then里面的回调
    while (this.onFulfilledCallbacks.length) { // 当异步成功回调数组中存在回调函数,那就执行
      this.onFulfilledCallbacks.shift()(this.value)
    }
  }

  reject(reason) {
    if (this.status !== 'pending') return
    this.status = 'rejected';
    this.value = reason;
    while (this.onRejectedCallbacks.length) { // 当异步失败回调数组中存在回调函数,那就执行
      this.onRejectedCallbacks.shift()(this.value)
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val  // 判断.then的第一个参数是不是一个函数,如果不是就直接作为结果返回
    onRejected = typeof onRejected === 'function' ? onRejected : val => { throw val } // 判断.then的第二个参数是不是一个函数,如果不是就直接作为错误返回

    var thenPromise = new myPromise((resolve, reject) => {  // 因为.then返回的是一个心的Promise对象

      const resolvePromise = callback => {  // 用于判断回调函数的类型
        setTimeout(() => {   // 让整个回调函数比同步代码晚一点执行,官方不是使用setTimeout实现
          try {
            const x = callback(this.value);
            if (x === thenPromise) {  // 你正在返回自身
              throw new Error('不允许返回自身!');
            }
            if (x instanceof myPromise) { // 返回的是一个Promise对象
              x.then(resolve, reject);
            } else { // 直接返回一个值,作为resolve的值,传递给下一个.then
              resolve(x);
            }
          } catch (error) {
            reject(error);
            throw new Error(error)
          }
        })
      }

      if (this.status === 'fulfilled') {
        resolvePromise(onFulfilled)
      } else if (this.status === 'rejected') {
        resolvePromise(onRejected)
      } else if (this.status === 'pending') {
        this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled));
        this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected));
      }
    })
    return thenPromise
  }
}

写在最后

最后和大家分享一下,我是如何一步一步实现简易版的Promise

首先从Promise构造函数的特点,三种状态,状态一经改变就不再变化,所以在resolvereject的方法里面加上判断,如果不是'pending'状态,则直接return,这样就实现了状态一经发生改变则不再变化,因为.then里面回调的执行,是根据Promise的状态来执行,当状态为'fulfilled'时才执行.then第一个回调函数,装状态为'rejected'执行.then第二个回调函数,但是如果在Promise里面,在resolve或者reject的外面套上setTimeout,那么状态变更会加入到下一次宏任务队列里,那我们九就维护出两个数组,用来存放未执行的回调,当状态改变之后,在对应的resolvereject方法里去判断我们维护的未执行的回调函数的数组里是否有未执行的回调,如果有直接调用掉,并且因为.then返回的是一个Promise对象,所以我们不能直接把'onFulfilled',或者'onRejected'其中一个回调给返回出去,否则.then后面就不能再接.then,所以在then方法里面我们定义了一个resolvePromise函数,其目的就是在返回的'onFulfilled',或者'onRejected'外面套一层Promise对象,使得他后面能继续接.then的回调,在这个resolvePromise函数内部我们还添加了判断回调的类型,在官方的定义的Promise对象中,规定了回调不能是原Promise对象,另外两个判断是回调是一个Promise对象,以及如果不是Promise对象,那就直接resolve()出去

最后同步代码会优先于.then的执行,因为.then是异步代码中的微任务,只有宏任务执行完之后,微任务才会执行,所以在resolvePromise的回调外面套一层setTimeout,这样返回出去的.then的逻辑,会去到下一次的宏任务队列,这样就实现了.then的执行会比同步代码稍晚一些,但是官方并不是使用setTimeout实现的。

到此这篇关于详解JavaScript如何实现一个简易的Promise对象的文章就介绍到这了,更多相关JavaScript实现Promise对象内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • js 轮播效果实例分享

    js 轮播效果实例分享

    本文主要分享了基于js实现的轮播效果的实例代码,具有一定的参考价值,下面跟着小编一起来看下吧
    2016-12-12
  • 封装好的js判断操作系统与浏览器代码分享

    封装好的js判断操作系统与浏览器代码分享

    这篇文章主要介绍了封装好的js判断操作系统与浏览器代码分享,对于我们做系统、浏览器兼容非常有帮助,需要的朋友可以参考下
    2015-01-01
  • JavaScript多线程详解

    JavaScript多线程详解

    虽然有越来越多的网站在应用AJAX技术进行开发,但是构建一个复杂的AJAX应用仍然是一个难题。接下来小编给大家介绍JavaScript多线程,需要的朋友可以参考下
    2015-08-08
  • 微信小程序使用map组件实现获取定位城市天气或者指定城市天气数据功能

    微信小程序使用map组件实现获取定位城市天气或者指定城市天气数据功能

    这篇文章主要介绍了微信小程序使用map组件实现获取定位城市天气或者指定城市天气数据功能,涉及微信小程序map组件结合微信API获取天气信息相关操作技巧,需要的朋友可以参考下
    2019-01-01
  • 利用Bootstrap实现表格复选框checkbox全选

    利用Bootstrap实现表格复选框checkbox全选

    Bootstrap相信应该不用多介绍,来自 Twitter,是目前最受欢迎的前端框架。这篇文章主要给大家介绍了如何利用Bootstrap实现表格中的checkbox复选框全选效果,文中给出详细的介绍及完整的实例代码,相信对大家的理解和学习具有一定的参考借鉴价值,下面来一起看看吧。
    2016-12-12
  • js 为label标签和div标签赋值的方法

    js 为label标签和div标签赋值的方法

    这篇文章介绍了js 为label标签和div标签赋值的方法,有需要的朋友可以参考一下
    2013-08-08
  • three.js设置物体的缩放和旋转代码示例

    three.js设置物体的缩放和旋转代码示例

    最近在用three.js做三维模型的时候,需要通过鼠标滑轮向前来控制视角朝鼠标的位置放大,然后通过鼠标滑轮向后将视角复原,这篇文章主要给大家介绍了关于three.js如何设置物体的缩放和旋转的相关资料,需要的朋友可以参考下
    2023-11-11
  • JavaScript中valueOf函数与toString方法深入理解

    JavaScript中valueOf函数与toString方法深入理解

    基本上,所有JS数据类型都拥有valueOf和toString这两个方法,null除外。它们俩解决javascript值运算与显示的问题,本文将详细介绍,有需要的朋友可以参考下
    2012-12-12
  • 基于javascript的无缝滚动动画实现2

    基于javascript的无缝滚动动画实现2

    这篇文章主要介绍了基于javascript的无缝滚动动画实现2,文章通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • 如何threejs利用indexeddb缓存加载glb模型

    如何threejs利用indexeddb缓存加载glb模型

    这篇文章主要介绍了如何threejs利用indexeddb缓存加载glb模型问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04

最新评论