简单实现Promise

简单实现Promise

Promise在我们平时写js中经常用到,(如果你没有经常用到,也许你该好好学习一下ES6了!)但只有知道其内部实现原理才能更好的使用它,所以我们今天来一起深入学习Promise!

了解Promise

​ 学习一个东西,最好是从它的历史学起,为什么它会出现?为什么它更好?为什么它这样设计?保留这些问题,我们将一一解释。

不够优雅的callback

​ 基于单线程js的开发都快把人搞秃了,想象一种情形:用户点击提交你页面表单,为了让用户有更好的体验,我们不跳转式提交表单,那么我们就要在JavaScript线程中提交。但是我们很穷,服务器的带宽只有1Mbps,这会导致什么?JavaScript在提交表单后苦苦等待服务器响应,但是由于我们网速太慢,无法及时获得相应,用户也不耐烦在页面瞎点(永远不要信任用户!),用户会发现所有页面相应都无法获得(主线程在处理相应!),那他就会放弃这家网站!

​ 没错,单线程就会出现这些事情,后来聪明的工程师想出了一个办法,通过事件机制来回调异步操作!那么,上面情况就轻而易举的解决了!我们只需要监听ajaxonMessage方法,并在方法中调用我们的callback函数即可!

​ 回调虽然很好,但是有一个比较严重的问题!看下面例子。

ajax.onMessage(function(msg){
    let ajax2 = Ajax.get("www.domain/controller.com")
    ajax2.onMessage(function(msg){
        let ajax3 = Ajax.get(".....")
        ajax3.onMessage(function(msg){
            //....
        })
    })
})

​ 哦!回调越来越多,我们好像无法掌握这串代码了!这就是回调不足之处!回调地狱

拯救世界的Promise

​ 那么有没有一种方法,能让我们想同步一样解决异步问题呢?聪明的工程师又想到一种方法,我们应该将这些回调封装成一个函数,供我们随时使用,而且为了解决嵌套的可读性差,我们应该使用链式调用(相信我,链式真的比嵌套好)!

​ 来看一下Promise的使用:

//Ajax全为抽象ajax操作,不要将它带入任何实际操作!
//我们封装了一个ajax在Promise中的操作
let ajaxToPromise = url=>{
    return new Promise((resolve,reject)=>{
   		//为了方便在本地测试,我们不用ajax请求,我们改用setTimeOut方法,相信聪明的你一定能理解
        setTimeout(()=>{
            resolve()
            console.log("延迟 " + url + " ms的操作被完成!")
        },url);
	})
}
//封装完成 我们看看使用时能为我们提供什么便利
let url1,url2,url3;
ajaxToPromise(1000).then(()=>{return ajaxToPromise(1000)}).then(()=>{return ajaxToPromise(1000)})

​ 如果你运行,你会发现每隔1000ms就会有输出。

​ 很清楚,我们通过我们封装的ajaxToPromise方法,一行解决了异步请求多次问题!接下来,我们将会知道Promise究竟在做什么?

new Promise( function(resolve, reject) {...} /* executor */  );

​ 简单理解下:Promise构造函数仅仅接受函数作为参数,并在构造方法中立即调用该函数。以后Promise会作为该函数的代理,在该函数改变Promise状态值时,可以使用then()方法执行后续操作。

Promise的内部实现

如果你只对Promise的运行与使用感兴趣你可以跳过这部分,但是我会推荐任何人读它。

Promise内部使用回调函数调用then()方法注册的回调,在什么时候用?我们显式调用resolve()就会更改Promise状态,相应调用在then()中注册的回调。

​ 如果你看不太懂,也没有关系,毕竟语言是很抽象的,我们直接上代码,不用担心,我们不会直接写出Promise全部代码,一点一点来才利于学习。

构造函数实现

希望大家都喜欢ES6的语法!(笑

class MyPromise{
  /**
   * 构造函数 立即执行callback函数
   * @param {Function} callback 
   */
  constructor(callback) {
    if(this.isFunction(callback)){
      callback()
    }else{
      throw new Error("MyPromise 必须接受一个函数参数!")
    }
  }
  /**
   * 判断func是否为Function对象
   * @param {Function} func 
   */
  isFunction(func){
    return typeof func == "function"
  }
}

​ 构造方法已经完成,但是我们注意力应该放在then()方法和resolve()方法。下面我们先考虑then()方法的实现。

then()方法实现

then()方法应该去注册执行回调函数,而本身不执行,选择让用户在callback方法中执行是更好的选择。那么我们注册的方法应该存放一下,选择队列存放吧!我们维护两个队列onFulfilledQueueonRejectedQueue,我们创建一个方法initData(),其中初始化我们所有的成员变量。

class MyPromise{
  constructor(callback) {
    if(this.isFunction(callback)){
      this.initData()//初始化变量
      callback()
    }else{
      throw new Error("MyPromise 必须接受一个函数参数!")
    }
  }
    //...上面代码这里省略
   /**
   * 初始化MyPromise所谓成员变量
   */
  initData(){
    this.callback = null;//用户传入构造的callback
    this.onFulfilledQueue = []//注册的成功回调
    this.onRejectedQueue = []//注册的失败回调
    this.value = null;//要返回的成功值
    this.error = null;//要返回的失败值
  }
}

then()方法,接受最多两个参数onFulfilled,onRejected分别应当在状态是成功失败时调用,那么我们先引入状态!

// 定义Promise的三种状态常量
// 把下面代码加入你的initData()中
this.PENDING = 0//进行中
this.FULFILLED = 1//已完成
this.REJECTED = -1//以失败
this.status = this.PENDING//现在状态 默认为进行中

​ ok,到这一步,我们封装了成员变量初始化,为我们的Mypromise引入了状态机制,then()的雏形可以写出来了。

/**
   * then方法最多接受两个函数类型参数
   * 如果此时MyPromise状态为完成将会执行onFulfilled
   * 若为错误执行onRejected
   * @param {Function} onFulfilled 
   * @param {Function} onRejected 
   */
  then(onFulfilled, onRejected) {
    if(this.status == this.PENDING){//未完成时 加入队列
      if(this.isFunction(onFulfilled))
        this.onFulfilledQueue.push(onFulfilled)
      if(this.isFunction(onRejected))
        this.onRejectedQueue.push(onRejected)
    }else if(this.status == this.FULFILLED&& this.isFunction(onFulfilled)){
      onFulfilled(this.value)//如果这时候状态改变为已完成 我们直接调用onFulfilled
    }else if(this.status==this.REJECTED&&this.isFunction(onRejected)){
      onRejected(this.error)//这里同上面
    }
  }

​ 基本雏形出来了,但是我们还要考虑,因为then()要支持链式调用,所以我们应该在最后返回MyPromise的一个实例。

​ 但是又出现了新的问题,我们返回的MyPromise实例什么时候改变状态?我们应该依赖于上个MyPromisethen()方法的返回值,所以我们要大改代码

​ 继续完善代码

then(onFulfilled, onRejected) {
    return new MyPromise((onFulfilledNext, onRejectedNext) => {
      let fulfilled = value => {
        try {
          if (!this.isFunction(onFulfilled)) {
            onFulfilledNext(value)
          } else {
            let res = onFulfilled(value);
            if (res instanceof MyPromise) {
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              onFulfilledNext(res)
            }
          }
        } catch (error) {
          onRejectedNext(error)
        }
      }
      let rejected = error => {
        try {
          if (!this.isFunction(onRejected)) {
            onRejectedNext(error)
          } else {
            let res = onRejected(error);
            if (res instanceof MyPromise) {
              // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
              res.then(onFulfilledNext, onRejectedNext)
            } else {
              //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
              onFulfilledNext(res)
            }
          }
        } catch (err) {
          // 如果函数执行出错,新的Promise对象的状态为失败
          onRejectedNext(err)
        }
      }
      switch (this.status) {
        // 当状态为pending时,将then方法回调函数假如执行队列等待执行
        case this.PENDING:
          this.onFulfilledQueue.push(fulfilled)
          this.onRejectedQueue.push(rejected)
          break
          // 当状态已经改变时,立即执行对应的回调函数
        case this.FULFILLED:
          fulfilled(this.value)
          break
        case this.REJECTED:
          rejected(this.error)
          break
      }
    });
  }

​ 现在代码很完美,我们then中返回的MyPromise依赖于上个then的调用及返回值了。

实现_resolve_reject方法

​ 这一节就比较简单,我们要干的事情,就是判断状态,并全部调用队列中的函数。

  _resolve(value) {
    if (this.status != this.PENDING) return;
    this.status = this.FULFILLED;
    this.value = value;
    const run = () => {
      while (this.onFulfilledQueue.length != 0) {
        let cb = this.onFulfilledQueue.shift()
        cb(this.value)
      }
    }
    setTimeout(run, 0//要在MyPromise中支持同步操作 也就是resolve()之后操作先于then()中操作
               		//不太懂的同学可以看看我的有一篇详细讲解js中的event loop
  }
  _reject(error) {
    if (this.status != this.PENDING) return
    this.status = this.REJECTED
    this.error = error
    const run = () => {
      while (this.onRejectedQueue.length != 0) {
        let cb = this.onRejectedQueue.shift()
        cb(this.error)
      }
    }
    setTimeout(run, 0)
  }

catch实现

  catch(onRejected){
    console.log(onRejected)
    return this.then(null,onRejected)
  }

catch()方法本身只是then()方法的一种调用。

结尾

脑瘫码农 纯靠自学 如有错误 望请指正 共同学习 不胜感激

参考

发布了31 篇原创文章 · 获赞 38 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/yhy1315/article/details/99707014