自己实现一个Promise

2019-10-09 admin

最近看到好多讲解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

源代码在此。谢谢观看!

[转载]原文链接:https://segmentfault.com/a/1190000020617527

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-76891.html

文章标题:自己实现一个Promise

相关文章
js实现鼠标左右移动,图片也跟着移动效果
效果:鼠标往左移,图片对应右移,鼠标往右移,图片就左移动。图片距离越远偏移距离越大。 思路:首先获取图片原先的距离。设置一个变化值,图片的最终距离等于原先的距离加上变化值 布局:大盒子里面是图片,大盒子position:relative;图...
2017-02-17
vue.js实现请求数据的方法示例
vue2.0示例代码如下: var vm = new Vue({ el:"#list", data:{ gridData: "", }, ...
2017-03-20
vuejs通过filterBy、orderBy实现搜索筛选、降序排序数据
直接贴代码了: 先上输入前的样子: <style> #example{margin:100px auto;width:600px;} .show{margin:10px;} #searchText{display: block...
2017-03-17
js实现文字向上轮播功能
话不多说,请看实现代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8">...
2017-03-10
HTML5会是下一个风口吗?
2014年10月底, W3C(万维网联盟)正式宣布HTML5正式定稿,科技圈就像发现了可以打破谷歌、苹果所统领的原生APP世界的方法,发表了很多宣讲HTML5将真正开始颠覆原生(Native)App的文章,也开始着力发展HTML5,开始抢占...
2015-11-12
微信公众号实现无限制推送模板消息!可向指定openID群发
微信认证的服务号才有推送模板消息接口 所以本文需要在认证服务号的情况下学习 以上就是模板消息,只有文字和跳转链接,没有封面图。 在服务号的后台添加功能插件-模板消息即可。 模板消息,都是在后台选择一个群发模板的,然后获取模板ID,根据这...
2018-01-14
纯css实现窗户玻璃雨滴逼真效果
这里仅是用CSS技术来演示这样的一个场景,可能并不太实用。然而这是一个探索CSS新功能的最佳机会。可以让你尝试使用一些新特性和新工具。并且逐渐将在工作中实践。在制作窗口雨滴效果,将使用到HAML和Sass。 案例效果 看到上面的效果是不是...
2017-03-29
JavaScript实现简单的数字倒计时
这里是一个JavaScript中显示倒计时的方法,从10一秒一秒地往下减直到计时结束(即0),代码如下: runCount(10); function runCount(t){ if(t>0){ document.getE...
2017-03-23
canvas实现流星雨的背景效果
看到一个很棒的流星雨效果。修改一下样式就可以作为网页背景了。。! <!DOCTYPE html> <html> <head> <meta charset="utf-8&quot...
2017-03-09
js获取数组的最后一个元素
在js里面如何获取一个数组的最后一个元素呢?这里总结了两种方法,有需要的朋友可以看看。 (1)js内置pop方法 pop() 方法用于删除并返回数组的最后一个元素,注意这里在获取了数组的最后一个元素的同时也将原数组的最后一个元素给删除了。如...
2017-03-22
回到顶部