资讯专栏INFORMATION COLUMN

JavaScript五十问——浅入深出,自己实现一个 ES 6 Promise

hiyayiji / 3032人阅读

摘要:以上实现了最简单的一个测试代码当然,这不能算是一个,目前仅仅实现了根据状态调用不同的回调函数。静态函数接下来是的各种静态函数每一个都执行完毕后返回总结现在,一个完整的对象就完成了。

前言

说到 ES6,Promise 是绕不过的问题;如果说 ES6 的 Class 是基于 Javascript 原型继承的封装,那么 Promise 则是对 callback 回调机制的改进。这篇文章,不谈 Promise 的实际应用;聊一下 Promise 的实现原理,从最简单的解决方案入手,一步一步的自己实现一个 SimplePromise。

正文 入门

从最简单的 Promise 初始化和使用入手:

const pro = new Promise ((res, rej) => {})

pro.then(data => {}, err => {})

Promise 的构造函数如上,需要传递一个函数作为参数,这个函数有两个变量: resolve, reject。而 Promise 有不同的执行状态,分三种情况:Resolve, Reject, Pending。根据以上的信息,写出最基本的 SimplePromise 的类结构:

class SimplePromise{
  constructor(handler){
    this._status = "PENDING"
    handler(this._resolve.bind(this), this._reject.bind(this))//参数函数的作用域指向Class
  }

  _resolve(){}
  _reject(){}
}

接下来思考一下_resolve_reject两个函数的作用。我们知道,Promise 根据 then 方法来执行回调,而 then 是根据状态判断要执行的回调函数。不难推导出,_resolve_reject正是根据handler的执行来进行状态变更的,而状态只能由PendingResloveRejected转换,所以有:

class SimplePromise{
  constructor(handler){
   ...
  }

  _resolve(val){//异步返回的数据
    if(this._status === "PENDING"){//保证状态的不可逆性
      this._status = "RESOLVED"
      this._value = val
    }
  }
  _reject(val){
    if(this._status === "PENDING"){
      this._status = "REJECTED"
      this._value = val
    }
  }
}
then的调用逻辑

下面分析 then 函数的逻辑,从调用入手:

pro.then(data => {}, err => {})

then 接收两个参数,第一个是执行成功调用的函数,第二个是执行失败调用的函数。

class SimplePromise{
  constructor(handler){
    ...
  }

  _resolve(val){
   ...
  }
  _reject(val){
    ...
  }
  then(success, fail){
    switch (this._status){
      case "PENDING":
        break;
      case "RESOLVED":
        success(this._value)
        break;

      case "REJECTED":
        fail(this._value)
        break;
    }
  }
}

以上实现了最简单的一个 Promise
测试代码:

const pro = new SimplePromise(function(res, rej) {
    let random = Math.random() * 10
    if(random > 5){
      res("success")
    }
    else{
      rej("fail")
    }
})

pro.then(function(data) {
  console.log(data)
}, function(err) {
  console.log(err)
})

当然,这不能算是一个 Promise,目前仅仅实现了根据状态调用不同的回调函数。还没有实现异步。
那如何实现异步呢?关键在于 then 函数,当判断_statusPENDING时,如何延后调用 successfail函数,等待状态改变后再调用?

支持异步

这里采用数组来存储 fail 与 success 函数:

class SimplePromise{
  constructor(handler){
    this.status = "PENDING"
    this._onSuccess = []//存储fail 与 success 函数
    this._onFail = []
    handler(this._resolve.bind(this), this._reject.bind(this))
  }

  _resolve(val){
    if(this.status === "PENDING"){
      ...
      let temp
      while(this._onSuccess.length > 0){//依次执行onSuccess中的回调函数
        temp = this._onSuccess.shift()
        temp(val)
      }
    }
  }
  _reject(val){
    if(this.status === "PENDING"){
     ...
      let temp
      while(this._onFail.length > 0){
        temp = this._onFail.shift()
        temp(val)
      }
    }
  }

  then (success, fail){
    switch (this.status){
      case "PENDING":
        this._onSuccess.push(success)
        this._onFail.push(fail)
        break;
      ...
    }
  }
}

使用 onSuccess onFail 来存储回调函数,当处理状态为 PENDING 时,将回调函数 push 到相应的数组里,当状态变更后,依次执行数组里的回调函数。

测试代码:

const pro = new SimplePromise(function(res, rej) {
  setTimeout(function(){
    let random = Math.random() * 10
    if(random > 5){
      res("success")
    }
    else{
      rej("fail")
    }
  }, 2000)

})

pro.then(function(data) {
  console.log(data)
}, function(err) {
  console.log(err)
})

两秒后,会执行相应的回调。

到目前为止,最最最简单的一个 Promise 骨架已经基本完成了。但是还有很多功能待完成。现在可以稍微休息一下,喝个咖啡打个鸡血,回来我们会继续让这个 Promise 骨架更加丰满起来。

. . . . . .

完善Promise

欢迎回来,下面我们继续完善我们的 Promise。
上面完成了一个最基础的 Promise,然而还远远不够。首先,Promise 需要实现链式调用,其次 Promise 还需要实现 all race resolve reject 等静态函数。

首先,如何实现 then 的链式调用呢?需要 then 返回的也是一个 Promise。
于是有

class SimplePromise{
  ...
  then(success, fail){
      return new SimplePromise((nextSuccess, nextFail) => {
        const onFullfil = function(val){
          const res = success(val)
          nextSuccess(res)
        }

        const onReject = function(val){
          const res = fail(val)
          nextSuccess(res) ;
        }
        switch (this._status){
          case "PENDING":
            this._onSuccess.push(onFullfil)
            this._onFail.push(onReject)
            break;
          case "RESOLVED":
            onFullfil(this._value)
            break;

          case "REJECTED":
            onReject(this._value)
            break;
        }
      })
  }
}

测试代码:

const sp = new SimplePromise(function (res, rej){
  setTimeout(function(){
    let random = Math.random() * 10
    random > 5 ? res(random) : rej(random)
  }, 1000)
})

sp.then(data => {
  console.log("more than 5 " + data)
  return data
}, err =>{
  console.log("less than 5 " + err)
  return err
}).then((data) => {
  console.log(data)

})
then的参数限制

完成了链式调用,then 方法还有许多其他限制:
下面思考 以下问题:代码中四个使用 promise 的语句之间的不同点在哪儿?
假设 doSomething 也 doSomethingElse 都返回 Promise

doSomething().then(function () {
    return doSomethingElse();
}).then(finalHandler);

doSomething().then(function () {
    doSomethingElse();
}).then(finalHandler);;

doSomething().then(doSomethingElse()).then(finalHandler);;

doSomething().then(doSomethingElse).then(finalHandler);;

答案 一会儿再揭晓,我们先来梳理以下then 方法对传入不同类型参数的处理机制:
直接上代码:

class SimplePromise{
  ...
  then(success, fail){
    return new SimplePromise((nextSuccess, nextFail) => {
      const onFullfil = function(val){
        if(typeof success !== "function"){
          nextSuccess(val)
        }
        else{
          const res = success(val)//success 的返回值
          if(res instanceof SimplePromise){//如果success 返回一个promise 对象
            res.then(nextSuccess, nextFail)
          }
          else{
            nextSuccess(res)
          }
        }
      }
      if(fail){
        const onReject = function(val){
        if(typeof fail !== "function"){
          nextSuccess(val)
        }
        else{
          const res = fail(val)
          if(res instanceof SimplePromise){
            res.then(nextSuccess, nextFail)
          }
          else{
            nextSuccess(res)
          }
        }
      }
      }
      else{
        onReject = function(){}
      }
      switch (this._status){
      case "PENDING":
        this._onSuccess.push(onFullfil)
        this._onFail.push(onReject)
        break;
      case "RESOLVED":
        onFullfil(this._value)
        break;

      case "REJECTED":
        onReject(this._value)
        break;
      }
    })
  }
}

对于传入 then 方法的参数,首先判断其是否为 function,判断为否,直接执行 下一个 then 的 success 函数;判断为是,接着判断函数的返回值 res 类型是否为 Promise,如果为否,直接执行下一个 then 的 success 函数,如果为是,通过 then 调用接下来的函数。
所以,上面的问题就不难得到答案了。



doSomething().then(function () {
    return doSomethingElse();//返回值为Promise
}).then(finalHandler);

RETURN:
doSomething
          --->doSomethingElse(undefined)
                                        ---> final(doSomethingElseResult)

doSomething().then(function () {
  doSomethingElse();//返回值为 undefined
}).then(finalHandler);

RETURN:
doSomething
          --->doSomethingElse(undefined)
          ---> final(undefined)


doSomething().then(doSomethingElse())//参数 typeof != function
  .then(finalHandler);

RETURN:
doSomething
doSomethingElse(undefined)
            ---> final(doSomethingResult)

doSomething().then(doSomethingElse)//与1的调用方式是不同的
  .then(finalHandler);

RETURN:
doSomething
          --->doSomethingElse(doSomethingResult)
                                        ---> final(doSomethingElseResult)

好,then 方法已经完善好了。

静态函数

接下来是 Promise 的各种静态函数

class SimplePromise(){
  ...
  static all(){}
  static race(){}
  static resolve(){}
  static reject(){}
}
all
 static all(promiselist){
      if(Array.isArray(promiselist)){
        const len = promiselist.length;
        const count = 0
        const arr = []
        return new SimplePromise((res, rej) => {
          for(let i = 0; i {
              arr[i] = data
              count ++
              if(count === len){//每一个Promise都执行完毕后返回
                res(arr)
              }
            }, err => {
              rej(err)
            })
          }
        })
      }
    }
race
    static race(promiselist){
      if(Array.isArray(promiselist)){
        const len = promiselist.length
        return new SimplePromise((res, rej) =>{
          promiselist.forEach(item =>{
            this.resolve(item).then(data => {
              res(data)
            }, err =>{
              rej(err)
            })
          })
        })
      }
    }
resolve
    static resolve(obj){
      if(obj instanceof SimplePromise){
        return obj
      }
      else {
        return new SimplePromise((res) =>{
          res(obj)
        })
      }
    }
reject
    static reject(obj){
      if(obj instanceof SimplePromise){
        return obj
      }
      else {
        return new SimplePromise((res, rej) =>{
          rej(obj)
        })
      }
    }
总结

现在,一个完整的 Promise 对象就完成了。现在来总结一下 callback 回调和 Promise 的异同吧。
其实,不管是 callback 还是 Promise,这二者都是将需要滞后执行方法而提前声明的方式,只不过 callback 的处理方式比较粗犷,将 cb 函数放到异步执行的结尾;而 Promise 优于 cb 的是通过定义了不同的执行状态,更加细致的进行结果处理,提供了很好的 catch 机制,这是其一;其二,then 的链式调用解决了 cb 的回调地狱;但是 then 的链式调用也不是很好的解决方案,如果封装不好,then里面套用大量的代码的话也会引起代码的不美观和阅读上的困难,这一方面的终极解决方法还是 es7 的 async/await。

后记

这篇文章的代码是几个星期以前写的,参考的是思否上的一篇关于promise的文章;总结的是我对promise的理解和思考,如果有不准确或错误的地方还希望各位不吝赐教!希望大家积极反馈,一起交流!

参考文档

谈一谈使用 Promise 的反模式

写这篇文章的时候,我是参考我两周前的代码写的,当时的代码思路来源于思否上的谋篇博客,等我找到会贴上来

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100951.html

相关文章

  • 浅入深出setState(上篇)

    摘要:点燃引擎这是一个组件实现组件可交互所需的流程,输出虚拟,虚拟转为,再在上注册事件,事件触发修改数据,在每次调用方法时,会自动执行方法来更新虚拟,如果组件已经被渲染,那么还会更新到中去。 Part one - setState点燃引擎 showImg(https://segmentfault.com/img/bVbdGkJ?w=849&h=270); 这是一个React组件实现组件可交互...

    wow_worktile 评论0 收藏0
  • Javascript 十问——实现的继承多种方式

    摘要:组合继承实现了属性分离,方法共享下的完美继承方案继承我们的主角,,就是对组合继承的改进。这也是为什么在子类构造函数中一定要显示调用的原因。 谈到继承,或者更广义上的:一个对象可以使用另外一个对象的属性或方法。实现起来无外乎有两种方式:apply or call 改变this的作用域原型继承 改变__proto__指向,添加作用域链 而JavaScript所有的继承实现,都是围绕以上两点...

    BlackHole1 评论0 收藏0
  • JavaScript 十问——从源码分析 ES6 Class 的实现机制

    摘要:防止类的构造函数以普通函数的方式调用。这个函数的主要作用是通过给类添加方法,其中将静态方法添加到构造函数上,将非静态的方法添加到构造函数的原型对象上。 Class是ES6中新加入的继承机制,实际是Javascript关于原型继承机制的语法糖,本质上是对原型继承的封装。本文将会讨论:1、ES6 class的实现细2、相关Object API盘点3、Javascript中的继承实现方案盘点...

    LeexMuller 评论0 收藏0
  • Javascript十问——从源头细说Webpack与Gulp

    摘要:前言与是目前圈子内比较活跃的前端构建工具。对于初学者来说,对这二者往往容易认识不清,今天,就从事件的源头,说清楚与。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。打包后形成的文件出口。 前言:Webpack 与 gulp是目前圈子内比较活跃的前端构建工具。网上有很多二者比较的文章,面试中也会经常遇到gulp,Webpack的区别这样的问题。对于初学者来说,对这二...

    lwx12525 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<