自己实现一个Promise

最近看到好多讲解Promise源码解析,或者自己实现一个Promise的文章,突然想自己造个轮子试一试。

先说明一下,该轮子并不完全遵守任何标准和规范,只是对正规Promise使用后的理解和感受而编写的,纯属兴趣研究。

下面是实现代码:

// 对象类型判断
const is = (typeAsString) => obj => Object.prototype.toString.call(obj) === typeAsString

// 判断是否为一个Error对象
const isError = is('[object Error]')

/**
 * 自定义Promise对象
 * @param {(resolve: (value: any) => void, reject: (reason: any) => void) => void} executor 
 */
function MyPromise(executor) {
  this.executor = executor
  // 初始状态为pending...
  this.status = 'pending'

  /**
   * 内部判定状态,只有处于pending状态才可继续执行
   * @param {number} value 
   */
  function resolve(value) {
    if (this.status === 'pending') { // 确保只执行一次
      this.onfulfilled = ___onfulfilled.call(this, value)
    }
  }

  /**
   * 为了缓存resolve方法的执行结果
   * @param {*} value value就是resolve方法的执行结果
   */
  function ___onfulfilled(value) {
    this.status = 'fulfilled'   // 更改内部状态
    /**
     * @param {(value: number) => void} onfulfilled 这里是then方法中传入的参数
     */
    return (onfulfilled) => {
      return onfulfilled(value) // 
    }
  }

  /**
   * 触发异常的方法
   * @param {string} reason 
   */
  function reject(reason) {
    if (this.status === 'pending') {  // 确保只执行一次
      this.onrejected = ___onrejected.call(this, reason)
    }
  }

  /**
   * 
   * @param {Error} reason 如果传入的不是一个Error对象,那么会使用Error包装一下
   */
  function ___onrejected(reason) {
    this.status = 'rejected'    // 更改内部状态
    return (onrejected) => {
      reason = isError(reason) ? reason : new Error(reason)
      return onrejected(reason)
    }
  }

  /**
   * @param {(value: number) => any} onfulfilled 处理成功的函数
   * @param {(reason: any) => void} onrejected 处理出现异常的函数,如果传入该参数,那么catch方法就捕获不到了
   */
  this.then = function(onfulfilled, onrejected) {
    const self = this
    return new MyPromise((resolve, reject) => {
      setTimeout(function waitStatus() {
        switch (self.status){
          case 'fulfilled': // resolved
            if (onfulfilled) {
              // 将onfulfilled方法的返回值交给resolve,确保下一个.then方法能够得到上一个.then方法的返回值
              const nextValue = self.onfulfilled(onfulfilled) 
              resolve(nextValue)
            } else {
              resolve()   // 没有传入参数,假装传入了参数:)
            }
            break
          case 'rejected':  // rejected
            if (!onrejected) { // 如果没有传递onrejected参数,默认实现一个,确保catch方法能够捕获到
              onrejected = (reason) => reason
            }
            const nextReject = self.onrejected(onrejected)
            reject(nextReject)
            break
          case 'pending':   // 
          default:
              setTimeout(waitStatus, 0) // 只要是pending状态,继续等待,直到不是pending
              break
        }
      }, 0);
    })
  }

  /**
   * 捕获异常
   * @param {(reason: any) => void} onrejected
   */
  this.catch = function(onrejected) {
    const self = this
    setTimeout(function reject() {
      if (self.status === 'rejected') {
        self.onrejected(onrejected)
      } else {
        setTimeout(reject, 0);
      }
    }, 0);
  }

  // 直接执行
  this.executor(resolve.bind(this), reject.bind(this));
}

目前不考虑参数传入的正确性,假设传入的参数全部是正确的情况下,在nodejs环境下能够正常运行。以下是测试代码:

// 覆盖nodejs环境中默认的Promise对象
global.Promise = MyPromise

async function test () {
  const r = await new Promise((resolve) => {
    setTimeout(() => {
      resolve(121)
    }, 1000);
  })
  console.log(r)    // 打印121
}

test()

以上代码主要是使用setTimeout方法去检查Promise对象内部的状态,一旦发生变化立即作出相应的处理。

因为是靠setTimeout方法检查Promise内部的状态,所以属于宏指令任务,执行的优先级不像正规Promise那么高。

setTimeout(() => {
  console.log(1)
}, 0);

new MyPromise((resolve, reject) => {
  console.log(2)
  resolve(3)
}).then(v => {
  console.log(v)
})

上面代码的输出是2,1,3 正规Promise的输出结果应该是2,3,1

源代码在此。谢谢观看!

原文链接:segmentfault.com

上一篇:【JS 口袋书】第 3 章:JavaScript 函数
下一篇:明白Node.js中的Worker Threads

相关推荐

  • 😀一个原生js弹幕库

    danmujs 😀一个原生js弹幕库,基于 CSS3 Animation 地址、核心代码 本项目基于 rcbullets,项目约70%的代码基于rcbullets,首先要感谢这个项目的作者,如...

    6 个月前
  • 🔥 Promise|async|Generator 实现&原理大解析 | 9k字

    笔者刚接触async/await时,就被其暂停执行的特性吸引了,心想在没有原生API支持的情况下,await居然能挂起当前方法,实现暂停执行,我感到十分好奇。好奇心驱使我一层一层剥开有关JS异步编程的...

    5 个月前
  • 🎸 一篇文章搞懂前后端路由及前端路由的实现

    一、WEB 开发中的路由(以下简称路由)是指什么 路由简单来说根据网页 url 的改变来转发给对应控制器处理的应用程序,控制器就是我们俗称的Controller,一般是一个函数,所以路由也可以看作是u...

    1 个月前
  • (通用 Socket.io、push-notification)教你实现 web 网站实时推送系统消息到“电脑任务通知栏”

    消息通知是 web 网站的一个基本常规功能。web 网站消息管理模块一般都是用户主动请求消息通知,即使采用 socket 技术,也仅仅只是在用户浏览当前系统网站时,才能发现系统推送的消息通知,这样很容...

    2 个月前
  • (源码分析)为什么 Vue 中 template 有且只能一个 root ?

    引言 今年,疫情并没有影响到各种面经的正常出现,可谓是络绎不绝(学不动...)。然后,在前段时间也看到一个这样的关于 Vue 的问题,为什么每个组件 template 中有且只能一个 root? 可能...

    4 个月前
  • (小小黑科技)vue+echarts实现半圆图表

    如何用echarts实现半圆图表?在echarts官方实例倒腾一波,发现官方并没有提供半圆图表的写法,那怎么办呢?官方没提供,但需求还是要实现的。 半圆图表其实就是饼图的一半,那么简单的思路如下:设...

    1 年前
  • (初级前端)面试如何写出一个满意的深拷贝

    前言 已经有很多关于深拷贝与浅拷贝的文章,为什么自己还要写一遍呢💯 ❝ 学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。

    1 个月前
  • 龙骨换装游戏系统设计与实现(基于Egret+DragonBones龙骨动画)

    如何开发一个可维护性,可扩展,跨平台的换装游戏呢,本文从产品设计、龙骨动画、前端再到后端如何管理等角度,来介绍骨骼换装游戏从0到1的实现过程。 项目背景   我们一直在思考,如何能激励学员自主学习的积...

    4 个月前
  • 鼠标事件实现拖拽

    为什么需要拖拽 当前的互联网用户早已习惯了拖拽,习惯了拖拽带来的便利。任何一个前端项目都有加入拖拽这个功能的可能性。 拖拽的实现方案 鼠标事件实现 drag and drop api 这两种实现方...

    6 个月前
  • 高程3版中《寄生组合式继承》中的一个错误

    前言 最近一直在回顾js继承方式,在阅读《高级程序设计》第3版 的时候遇到一个问题,下面仅个人看法,如果有理解错误或者不同看法,欢迎一起探讨: 正文 何谓寄生组合继承,实质上分为两步: 1...

    2 年前

官方社区

扫码加入 JavaScript 社区