[class,SimplePromise,Promise,函数,回调函数]JavaScript之实现一个ES6Promise-阿里云开发者社区

开发者社区> dasein58> 正文

[class,SimplePromise,Promise,函数,回调函数]JavaScript之实现一个ES6Promise

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

  说到 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的执行来进行状态变更的,而状态只能由Pending向Reslove或Rejected转换,所以有:

  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 函数,当判断_status为PENDING时,如何延后调用 success与fail函数,等待状态改变后再调用?

  支持异步

  这里采用数组来存储 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。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
java回调函数
    在WINDOWS中,程序员想让系统DLL调用自己编写的一个方法,于是利用DLL当中回调函数(CALLBACK)的接口来编写程序,使它调用,这个就称为回调。     在Java里面,我们使用接口来实现回调。     所谓的回调,就是程序员A写了一段程序(程序a),其中预留有回调函数接口,并封装好了该程序。程序员B要让a调用自己的程序b中的一个方法,于是,他通过a中的接口回调自己b中的方法
1345 0
从零开始学_JavaScript_系列(四)——jquery(基础,选择器,触发条件,动画,回调函数)
jQuery语法   (1)引用jquery文件及下载库: http://jquery.com/download/ 下载 Download the compressed, production jQuery 2.2.2 这个是用户版的,已经被精简和压缩。 然后使用 <script src="jquery.js"></script> 来启动这个库文件
2460 0
从零开始学_JavaScript_系列(四)——jquery(基础,选择器,触发条件,动画,回调函数)
jQuery语法   (1)引用jquery文件及下载库: http://jquery.com/download/ 下载 Download the compressed, production jQuery 2.2.2 这个是用户版的,已经被精简和压缩。 然后使用 <script src="jquery.js"></script> 来启动这个库文件
1227 0
java 中的异步回调
异步回调,本来在c#中是一件极为简单和优雅的事情,想不到在java的世界里,却如此烦琐,先看下类图: 先定义了一个CallBackTask,做为外层的面子工程,其主要工作为start 开始一个异步操作,然而真正干活的是CallBackBody,它里面的execute才是真正要处理的事情,如果成功,则触发onSucess,否则触发onFailure。
720 0
MFC Activex OCX Javascript 互相访问问题,线程回调javascript
//比较好的教程    ocx 在 win7 系统会出现注册需要管理员权限的问题,这时候需要用管理员身份运行 cmd,然后运行 regsvr32注册。   很麻烦   尝试使用 nsis 做成安装包, 采用 regdll 注册 ocx, 成功。
1133 0
Java回调函数的理解
所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。例如Win32下的窗口过程函数就是一个典型的回调函数。一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C不得不提供。
998 0
从零开始学_JavaScript_系列(四)——jquery(基础,选择器,触发条件,动画,回调函数)
jQuery语法   (1)引用jquery文件及下载库: http://jquery.com/download/ 下载 Download the compressed, production jQuery 2.2.2 这个是用户版的,已经被精简和压缩。 然后使用 <script src="jquery.js"></script> 来启动这个库文件
1304 0
转Java 回调函数的理解
所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。例如Win32下的窗口过程函数就是一个典型的回调函数。一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C不得不提供。
694 0
+关注
741
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载