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

目录
  • 前言
  • Promise的基础结构与用法
  • 使用class类实现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对象内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript模拟实现Promise功能的示例代码

    模拟Promise的功能,  按照下面的步骤,一步一步 1. 新建是个构造函数 2. 传入一个可执行函数 函数的入参第一个为 fullFill函数 第二个为 reject函数: 函数立即执行, 参数函数异步执行 3. 状态一旦更改就不可以变更 只能 pending => fulfilled 或者 pending => rejected 4. then 的时候要处理入参的情况 successCallback 和failCallback 均可能为非函数 默认的 failCallback 一定要将异

  • Promise抛出错误解决基础示例详解

    目录 then catch finally Promise.resolve Promise.reject then then 函数的会接收两个回调函数,一个是 onFulfilled 函数,一个是 onRejected 函数 如果这两个回调函数没有写返回值,默认会 return undefined; 进入下一个函数的 onFulfilled 函数中 const p = new Promise((resolve, reject) => { resolve(22); }); p.then( (suc

  • JavsScript中Promise的错误捕获详解

    目录 我们需要在异步任务中准确的进行错误捕获,以便我们可以知道错误出在什么地方 我们再讨论then方法中的第二个参数和Promise.catch方法的区别 题: then方法的连续调用,怎么能够知道是第几个then方法报错了呢. 总结 我们需要在异步任务中准确的进行错误捕获,以便我们可以知道错误出在什么地方 如果对Promise和trycatch不够理解的话,很多时候会出现Promise中的错误无法被捕获的情况,本文来讨论这些情况 try catch try catch 只能捕获当前上下文中的错

  • JavaScript面试必考之实现手写Promise

    目录 Promise手写 框架 完整代码 测试 resolve reject Promise手写 Promise作为面试必考题,Promise的手写也是面试官必问的问题,所以对于Promise我们一定要了解透彻 框架 (function(window) { MyPromise.prototype.then = function (onResolved, onRejected) {} MyPromise.prototype.catch = function (onRejected) {} func

  • Promise对象all与race方法手写示例

    目录 前言 Promise.all 介绍 手写 Promise.race 介绍 手写 前言 在理解了手写promsie.then的方法后,再来看它的其他方法,感觉真的简单了不少. Promise.all 介绍 Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例. const p = Promise.all([p1, p2, p3]); 上面代码中,Promise.all()方法接受一个数组作为参数,p1.p2.p3都是 Promise 实例.另外

  • JS使用Promise时常见的5个错误总结

    目录 1.避免 Promise 地狱 2.在 Promise 中使用 try/catch 块 3.在 Promise 块内使用异步函数 4.在创建 Promise 后立即执行 Promise 块 5.不一定使用 Promise.all() 方法 总结 Promise 提供了一种优雅的方法来处理 JS 中的异步操作.这也是避免“回调地狱”的解决方案.然而,并没有多少开发人员了解其中的内容.因此,许多人在实践中往往会犯错误. 在本文中,介绍一下使用 promise 时的五个常见错误,希望大家能够避免

  • 捕获未处理的Promise错误方法

    为了保证可读性,本文采用意译而非直译,并且对源代码进行了大量修改.另外,本文版权归原作者所有,翻译仅用于学习. 使用Promise编写异步代码时,使用reject来处理错误.有时,开发者通常会忽略这一点,导致一些错误没有得到处理.例如: function main() { asyncFunc() .then(···) .then(() => console.log('Done!')); } 由于没有使用catch方法捕获错误,当asyncFunc()函数reject时,抛出的错误则没有被处理.

  • Promise 链式调用原理精简示例

    目录 前言 代码 经典案例 解析 第一步 第二步 分析说明,此过程需结合上文中的案例一起阅读 总结 前言 在面试的过程中,总有一些面试官会问你,手写一个简易版的Promise得行不,得行的话就写一个出来看看,啪一哈,就把纸和笔给了你. 我们思索半天就写出来了个下面这个. 哦豁,高薪张开了它的翅膀,远离了我们. class Promise { constructor (resolve, reject) {} resolve () {} reject (){} then () {} catch ()

  • 详解JavaScript异步编程中jQuery的promise对象的作用

    Promise, 中文可以理解为愿望,代表单个操作完成的最终结果.一个Promise拥有三种状态:分别是unfulfilled(未满足的).fulfilled(满足的).failed(失败的),fulfilled状态和failed状态都可以被监听.一个愿望可以从未满足状态变为满足或者失败状态,一旦一个愿望处于满足或者失败状态,其状态将不可再变化.这种"不可改变"的特性对于一个Promise来说非常的重要,它可以避免Promise的状态监听器修改一个Promise的状态导致别的监听器的行

  • 详解JavaScript如何创建一个非自动播放的GIF网络组件

    目录 一些很可爱的测试数据 构建Web组件 逻辑 结果 今天,我将向您展示如何创建一个允许您的用户决定是否要播放 gif 的 Web 组件!让我们开始吧. 一些很可爱的测试数据 这里用的gif是小骆驼和猫的这种可爱互动: 哇,太可爱了!我可以看一天这个 构建 Web 组件 对于这个 Web 组件,我们需要一些东西: 画布(“缩略图”所在的位置) 一张图片(实际的 gif) 标有“gif”的标签 一些造型 让我们这样做: const noAutoplayGifTemplate = document

  • 万字详解JavaScript手写一个Promise

    目录 前言 Promise核心原理实现 Promise的使用分析 MyPromise的实现 在Promise中加入异步操作 实现then方法的多次调用 实现then的链式调用 then方法链式调用识别Promise对象自返回 捕获错误及 then 链式调用其他状态代码补充 捕获执行器错误 捕获then中的报错 错误与异步状态的链式调用 将then方法的参数变成可选参数 Promise.all方法的实现 Promise.resolve方法的实现 finally方法的实现 catch方法的实现 完整

  • 详解JavaScript面向对象实战之封装拖拽对象

    目录 概述 1.如何让一个DOM元素动起来 2.如何获取当前浏览器支持的transform兼容写法 3.如何获取元素的初始位置 5.我们需要用到哪些事件? 6.拖拽的原理 7. 我又来推荐思维导图辅助写代码了 8.代码实现 part1.准备工作 part2.功能函数 part3.声明三个事件的回调函数 9.封装拖拽对象 概述 为了能够帮助大家了解更多的方式与进行对比,我会使用三种不同的方式来实现拖拽. 不封装对象直接实现: 利用原生JavaScript封装拖拽对象: 通过扩展jQuery来实现拖

  • 详解JavaScript基础知识(JSON、Function对象、原型、引用类型)

    1.JSON 概念:JavaScript 对象表示法(JavaScript Object Notation),是一种轻量级的数据交换格式 特点:易于程序员编写和查看:易于计算机解析和生成 数据结构:Object对象格式   { key: value , key: value } Array数组格式   [ value , value ] Eg:  var json = ' { "   " : ''  '' } '    (内部双引号,外部单引号) * JSON - 支持独立的"

  • 详解JavaScript中的数组合并方法和对象合并方法

    1 数组合并 1.1 concat 方法 var a=[1,2,3],b=[4,5,6]; var c=a.concat(b); console.log(c);// 1,2,3,4,5,6 console.log(a);// 1,2,3 不改变本身 1.2 循环遍历 var arr1=['a','b']; var arr2=['c','d','e']; for(var i=0;i<arr2.length;i++){ arr1.push(arr2[i]) } console.log(arr1);/

  • 详解JavaScript状态容器Redux

    目录 一.Why Redux 二.Redux Data flow 三.Three Principles(三大原则) 四.Redux源码解析 4.1.index.js 4.2.createStore.js 4.3.combineReducers.js 4.4.bindActionCreators.js 4.5.compose.js 4.6.applyMiddleware.js 五.从零开始实现一个简单的Redux 六.Redux Devtools 七.总结 一.Why Redux 在说为什么用 R

  • 详解JavaScript实现监听路由变化

    目录 history pushState()方法 pushState()使用场景 replaceState() 方法 popstate事件 pushState和replaceState如何监听呢? 获取当前状态 对比 总结 前端实现路由变化主要有两种方式,这两种方式最大特点就是实现URL切换无刷新功能 通过hash改变,利用window.onhashchange 监听. 通过history的改变,进行js操作加载页面,然而history并不像hash那样简单,因为history的改变,除了浏览器

  • 详解JavaScript基于面向对象之创建对象(2)

    接着上文<详解JavaScript基于面向对象之创建对象(1)>继续学习. 4.原型方式        我们创建的每个函数都有一个通过prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法.逻辑上可以这么理解:prototypt通过条用构造函数而创建的那个对象的原型对象.使用原型的好处就是可以让所有对象实例共享它所包含的属性和方法.也就是说,不必在构造函数中定义对象信息,而是直接将这些信息添加到原型中.        原型方式利用了对象的pr

  • 详解JavaScript基于面向对象之继承

    一.面相对象继承机制       这个实例使用UML很好的解释了继承机制.       说明继承机制最简单的方式是,利用一个经典的例子就是几何形状.实际上,几何形状只有两种,即椭圆形(是圆形的)和多边形(具有一定数量的边).圆是椭圆的一种,它只有一个焦点.三角形.矩形和五边形都是多边形的一种,具有不同数量的边.正方形是矩形的一种,所有的边等长.这就构成了一种完美的继承关系,很好的解释了面向对象的继承机制.        在这个例子中,形状是椭圆形和多边形的基类(通常我们也可以叫它父类,所有类都由

随机推荐