前端Promise实现(二) | 初级版本的实现

/* =====================版本二========================== */

/**
 * 版本二
 * 可以发现,版本一中的ES5的回调写法在业务复杂之后会形成回调地狱,难以维护的。
 * 版本二来初步探讨Promise的实现功能,
 * 为什么Promise能够解决或者是说极大的减轻了这种回调地狱
 */


/**
 * 先来看Promise利用的原理:利用new操作符,自动执行constructor函数
 * 
 * 毫无疑问的,Person的constructor函数是在new时候自动执行的
 * 给constructor函数传入一个参数fn,然后执行fn
 * 
 * 结合实例来看,new Person(foo),将foo作为参数传进去,然后foo会被执行
 * 
 * 以下的代码为了方便,统一使用一个Person类来代替,就不重复写Person了
 */

/**
 * foo传入Person后,会在Person的constructor函数中自动的被调用执行
 */
class Person {
  constructor(fn) {
    fn()
  }
}

function foo() {
  console.log('foo执行了')
}

new Person(foo)



/**
 * 第一步
 * 改造一下foo和Person的位置联系,省略写Person了,因为都是一样的流程
 */
new Person(function foo() {

  console.log('foo执行了')
})


/**
 * 第二步
 * 再次改造一下,因为传入的就是一个函数,使用 匿名函数 或者再到 箭头函数 即可
 * 把函数foo变为一个箭头函数,实现的功能还是一样的
 */

new Person(() => {
  console.log('foo执行了')
})


/**
 * 第三步
 * 如果在原有的业务逻辑代码上,将这些业务逻辑代码放在传入的函数foo(改造为箭头函数)中
 * 因为传入的函数foo会被自动的执行,那么foo函数里面的业务逻辑代码也同样会被执行
 * 
 * console.log('业务逻辑代码块!') 代表业务逻辑
 * 可以等效为
 */

//  console.log('业务逻辑代码块!')
new Person(function foo() {

  /**
   * 业务逻辑代码块区域,用一行表示很多的业务逻辑
   */
  console.log('业务逻辑代码块!')
})


/**
 * 第四步
 * 一般认为处理这些逻辑用在是异步任务上,当然同步任务也是可以的
 * 将异步任务代码块放入foo中,实现的效果还是和原来直接执行一样,
 * 因为foo会被自动的执行,所以将原有的代码块放入foo中,同样的也会被执行
 */

new Person(function foo() {

  /**
   * 原有的含有异步任务的代码块放入函数foo中,和直接执行是一样的效果
   * new自动调用的constructor函数是一个同步任务的过程!不是异步的过程
   * 和原有的直接执行异步任务一样,
   * new在调用的过程中,自动触发了要执行了这些异步任务的代码块,
   * 这些异步任务就被JS引擎放入了异步任务队列。
   * 
   * 用一个简单的定时器表示复杂的异步任务逻辑,如网络请求,IO操作等。
   */
  setTimeout(() => { // 异步任务代码块
    console.log('异步任务执行')
  },1000)
})


/**
 * 第五步
 * 完成了相同功能的实现后,
 * 还要想着
 * 仅仅只这样无法完成业务后续的逻辑功能,即成功了执行哪些逻辑,失败了执行哪些逻辑
 * 原来还要传入成功的回调和失败的回调,这两个回调函数参数怎么设置。
 * 
 * 优化的逻辑是不能将成功回调success或者失败回调fail传入原有的代码中,
 * 而是将异步任务和两个回调任务分开实现,否则还是会形成回调地狱。
 * 
 * 那么如何实现呢?这里必然要有一个信息来通知异步任务执行成功还是执行失败
 * 
 * promise利用了两个信号resolve和reject
 * 这两个是函数类型的参数,是什么类型不重要,可以将这两个参数看成是通讯员
 * 一个通知异步任务执行成功了,一个通知执行失败了,而函数刚好有这个功能(进行调用)
 * 
 * 在异步任务中进行判断结果,只有在异步任务中才能判断
 * 如果成功了就用resolve通知,如果失败了就用reject通知
 */

new Person(function foo() {

  /**
   * 原有的含有异步任务的代码块放入函数foo中,和直接执行是一样的效果
   * 用一个简单的定时器表示复杂的异步任务逻辑,如网络请求,IO操作等。
   * 
   * 注意:一定是在异步任务中进行判断异步任务成功还是失败!
   */
  setTimeout(() => { // 异步任务代码块
    console.log('异步任务执行')
    
    // 判断成功还是失败 resolve(full filed) or reject
  }, 1000);
})


/**
 * 第六步
 * resolve和reject两个参数怎么放,哪里传入比较合适
 * 思考:因为要在异步任务中判断,
 * 如果成功就用resolve通知,如果失败就用reject通知
 * 所以异步任务必然要能够读取到这两个参数,
 * 而再编写代码传入时,传入的只有foo函数,并且异步代码块也是在foo里面,
 * 所以放在foo函数的参数里面。
 * 
 * Promise规定第一个参数就是resolve,第二个参数就是reject
 * 这样定义就是规范的制定,就是规定好第一个参数是什么,第二个参数是什么,
 * 规范制定好了,就不用在查看文档检查参数是什么了。
 * 
 * 传入参数resolve,reject
 */
new Person(function foo(resolve, reject) {
  
  /**
   * 原有的含有异步任务的代码块放入函数foo中,和直接执行是一样的效果
   * 用一个简单的定时器表示复杂的异步任务逻辑,如网络请求,IO操作等。
   * 
   * 注意:一定是在异步任务中进行判断异步任务成功还是失败!
   */
  setTimeout(() => { // 异步任务代码块
    console.log('异步任务执行')
    
    // 判断成功还是失败 resolve(full filed) or reject,假设成功
    // resolve()
  }, 1000);
})


/**
 * 第七步
 * 因为传入的参数是resolve,reject,但是没有相应的传入
 * 所以第六步中的resolve和reject还是一个undefined,如果写成resolve()那么肯定会报错
 * 
 * 怎么解决呢?
 * 思考一个问题:resolve和reject是我们传入呢还是直接在里面定义好?
 * resolve和reject的作用就是传递信息,通知异步任务是否成功执行,
 * 成功了用resolve通知,失败了用reject通知。
 * 
 * 怎么通知呢?
 * 用函数调用发送信息,resolve()或reject()
 * 
 * 这也要求必须resolve和reject要和Person关联在一块,
 * 
 * 因为之前说过,异步任务回调的后续(成功还是失败)应该和异步任务分开实现
 * 用resolve来通知成功,那么也就是说如果resolve就是成功回调函数success,
 * 代码还是和ES5一样,会形成回调地狱,同样的道理,reject也不能是失败回调fail
 * 
 * 那么怎么分开实现呢?
 * 还记得第一次用new自动调用constructor函数吗?
 * 利用了将函数A作为参数传入另外一个会自动执行的函数B里面,
 * 然后利用函数B能够执行的现象,调用了函数A,不就是这套逻辑吗?
 * 
 * 现在在异步任务中判断异步任务是否成功了,决定用resolve还是reject
 * resolve或reject可以被调用,也就是能够执行
 * 按照上面的推理,应该将成功回调success放入resolve的参数中,将失败回调fail放入reject中
 * 
 * 但是传入参数的时候,foo中传入的resolve是一个函数地址,不可能传入resolve(success)这种形式,
 * resolve(success)就表示执行resolve了,不现实。同时上面也说过resolve不能是success
 * resolve只能是信息传递的功能,要符合单一功能原则。reject同理
 * 
 * 那么怎么做才能够满足,resolve和reject只是信息传递员,又能够通知到外面呢?
 * 别忘记了,功能的实现一个函数A作为参数传入另一个能够被激活执行的函数B中,B中执行了A。
 * 
 * 以resolve为例
 * 既然resolve能够被激活执行,同时resolve中不能够承担success的调用。
 * 并且异步任务归异步任务的执行,后续的回调应该分开,那么利用一下对象的方法吧。
 * 互联网的解决方案永远都是加一层。这里就加一层方法。
 * 
 * 还是以resolve为例
 * Promise的实现就是加一个then方法,用resolve调用then方法,
 * 然后then方法能够执行了,满足要求添加了,那么将success传进去,就满足了自动调用了
 * 并且因为是对象的方法,和new的过程完全是分开的。
 * 
 * 这里将Person改为HyPromise,foo函数改为executor函数,并且给类添加then方法
 * 这里还需要理解ES6的类转function 函数对象,类中的非静态方法都是给对象使用的
 */

class HyPromise1 {
  constructor(executor) {
    executor()
  }
  then(success) {
    success()
  }
}

new HyPromise1(function executor(resolve, reject) {
  
  /**
   * 用一个简单的定时器表示复杂的异步任务逻辑,要在异步任务中判断是否成功。
   */
  setTimeout(() => { // 异步任务代码块
    console.log('异步任务执行')
    
    // 判断成功还是失败 resolve(full filed) or reject,假设成功
    // resolve()
  }, 1000);
})


/**
 * 第八步
 * 发现resolve reject还是不能调用,因为还不知道resolve reject哪里定义
 * 
 * 以resolve为例
 * 很简单了:要调用then,那么定义在HyPromise即可,
 * 定义在外部不知道怎么通信通知then,并且就算能够调用then,
 * 这个方法定义在外面还是很冗余,因为每次一个new HyPromise还要生成 resolve reject方法。
 * 
 * 改造HyPromise
 */
class HyPromise2 {
  constructor(executor) {
    executor()
  }
  resolve() { this.then() }
  reject() {
    //...
  }
  then(success) {
    success()
  }
}

new HyPromise2(function executor(resolve, reject) {
  
  /**
   * 用一个简单的定时器表示复杂的异步任务逻辑,要在异步任务中判断是否成功。
   */
  setTimeout(() => { // 异步任务代码块
    console.log('异步任务执行')
    
    // 判断成功还是失败 resolve(full filed) or reject,假设成功
    // resolve()
  }, 1000);
})


/**
 * 第九步
 * 还是发现resolve为undefined,因为没有传入东西
 * 类定义的方法是类定义的方法,自己传入的还是自己传入的,没有关联起来
 * 
 * 如果别人传入的是res和rej呢,此时应该将传入的参数和方法关联起来
 * 
 * 在哪关联?初始化对象的时候constructor函数中,resolve和reject都是在executor的参数,直接关联即可
 * 
 * 改造HyPromise
 */

 class HyPromise3 {
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  resolve() { this.then() }
  reject() {
    //...
  }
  then(success) {
    success()
  }
}

new HyPromise3(function executor(resolve, reject) {
  
  /**
   * 用一个简单的定时器表示复杂的异步任务逻辑,要在异步任务中判断是否成功。
   */
  setTimeout(() => { // 异步任务代码块
    console.log('异步任务执行')
    // 判断成功还是失败 resolve(full filed) or reject,假设成功
    resolve()
  }, 1000);
}).then(() => {
  console.log('then');
})


/**
 * 第十步
 * 会发现无法执行的,并且顺序也是错误的,then被直接调用了
 * resolve无法调用then,因为此时的this指向的是window或者是说undefined(node)
 * 因为在异步任务中有一个隐式绑定resolve()直接调用了
 * 
 * 因为实现的是class,而不是用function来实现,可以通过箭头函数来解决
 * 因为每次new的过程,生成的都是一个对象,而箭头函数无法改变this,并且此时的this也就是当前的对象了
 * 
 * 还要解决的问题是:then会被直接调用
 * 其实应该通过一个收集器收集then函数中传入的回调函数,
 * 最后resolve 或者 reject 调用的时候才进行调用回调函数
 * 
 * 但是then在调用的时候需要知道当前是在执行中还是在full filed还是rejected
 * 
 * 用一个状态来记录 status
 * 
 * 
 * 改造HyPromise
 */


 class HyPromise4 {

  constructor(executor) {

    this.status = 'pending'
    this.callbacks = []

    executor(this.resolve, this.reject)
  }
  resolve = () => {
    this.status = 'resolved'
    this.then()
  }
  reject = () => {
    this.status = 'rejected'
  }
  then = (success) => {
    if(success){
      this.callbacks.push(success)
    }
    if(this.status != 'pending')
      this.execute()
  }

  execute = () => {
    this.callbacks.forEach(fn => {
      fn()
    })
  }
}

new HyPromise4(function executor(resolve, reject) {
  
  /**
   * 用一个简单的定时器表示复杂的异步任务逻辑,要在异步任务中判断是否成功。
   */
  setTimeout(() => { // 异步任务代码块
    console.log('异步任务执行')
    // 判断成功还是失败 resolve(full filed) or reject,假设成功
    resolve()
  }, 1000);
}).then(() => {
  console.log('then');
})


/**
 * 十一步
 * 原有的Promise实现中,
 * then是能够接收两个参数的,一个是成功的回调,一个是失败的回调,
 * 完善HyPromise
 */
 class HyPromise5 {

  constructor(executor) {

    this.status = 'pending'
    this.success_callbacks = []
    this.fail_callbacks = []

    executor(this.resolve, this.reject)
  }
  resolve = () => {
    this.handler('resolved')
  }
  reject = () => {
    this.handler('rejected')
  }
  handler = (state) => {

    /* 只有pending状态才可以变化 */ 
    if(this.status === 'pending') {
      this.status = state

      switch (this.status) {
        case 'resolved':
          this.execute(this.success_callbacks)
          break;
        default:
          this.execute(this.fail_callbacks)
      }
    }
    // ... 其他情况不执行
  }
  then = (success, fail) => {
    if(success){
      this.success_callbacks.push(success)
    }
    if(fail) {
      this.fail_callbacks.push(fail)
    }
  }
  execute = (callbacks) => {
    callbacks.forEach(fn => {
      fn()
    })
  }
}

new HyPromise5(function executor(resolve, reject) {
  
  /**
   * 用一个简单的定时器表示复杂的异步任务逻辑,要在异步任务中判断是否成功。
   */
  setTimeout(() => { // 异步任务代码块
    console.log('异步任务执行')
    // 判断成功还是失败 resolve(full filed) or reject,假设成功
    reject()
    resolve()
  }, 1000);
}).then(() => {
  console.log('success then');
}, ()=> {
  console.log('fail then')
})


/**
 * 十二步
 * 可以发现第一版最初版本的功能都已实现,
 * 但是Promise支持链式调用,并且这个最初的版本还是有很多问题,比如说当错误发生时的处理等
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值