本站业务范围:1、PC端软件开发、网站开发 2、移动端APP、网站、微信接口、微商城开发 3、视频教程、课程设计和辅导 4、单片机开发 5、串口通讯调试
 当前位置:文章中心 >> Jquery_Ajax_Javascript
立即购买视频教程 如何停止执行Promise链后面的代码
夜鹰教程网 来源:www.yyjcw.com 日期:2019-1-4 12:54:24
如何停止执行Promise链后面的代码

这篇文章不能解决你的问题?我们还有相关视频教程云课堂 全套前端开发工程师培训课程

微信号:yyjcw10000 QQ:1416759661  远程协助需要加QQ!

业务范围:视频教程|程序开发|在线解答|Demo制作|远程调试| 点击查看相关的视频教程

技术范围:全端开发/前端开发/webapp/web服务/接口开发/单片机/C#/java/node/sql server/mysql/mongodb/android/。 



在使用Promise处理一些复杂逻辑的过程中,我们有时候会想要在发生某种错误后就停止执行Promise链后面所有的代码。


然而Promise本身并没有提供这样的功能,一个操作,要么成功,要么失败,要么跳转到then里,要么跳转到catch里。


如果非要处理这种逻辑,一般的想法是抛出一个特殊的Error对象,然后在Promise链后面的所有catch回调里,检查传来的错误是否为该类型的错误,如果是,就一直往后抛,类似下面这样


doSth()

.then(value => {

  if (sthErrorOccured()) {

    throw new Error('BIG_ERROR')

  }

  // normal logic

})

.catch(reason => {

  if (reason.message === 'BIG_ERROR') {

    throw reason

  }

  // normal logic

})

.then()

.catch(reason => {

  if (reason.message === 'BIG_ERROR') {

    throw reason

  }

  // normal logic

})

.then()

.catch(reason => {

  if (reason.message === 'BIG_ERROR') {

    throw reason

  }

  // normal logic

})

这种方案的问题在于,你需要在每一个catch里多写一个if来判断这个特殊的Error,繁琐不说,还增加了耦合度以及重构的困难。


如果有什么办法能直接在发生这种错误后停止后面所有Promise链的执行,我们就不需要在每个catch里检测这种错误了,只需要编写处理该catch块本应处理的错误的代码就可以了。


有没有办法不在每个catch里做这种判断呢?


办法确实是有的,那就是在发生无法继续的错误后,直接返回一个始终不resolve也不reject的Promise,即这个Promise永远处于pending状态,那么后面的Promise链当然也就一直不会执行了,因为会一直等着。类似下面这样的代码


Promise.stop = function() {

  return new Promise(function(){})

}


doSth()

.then(value => {

  if (sthBigErrorOccured()) {

    return Promise.stop()

  }

  // normal logic

})

.catch(reason => {// will never get called

  // normal logic

})

.then()

.catch(reason => {// will never get called

  // normal logic

})

.then()

.catch(reason => {// will never get called

  // normal logic

})

这种方案的好处在于你几乎不需要更改任何现有代码,而且兼容性也非常好,不管你使用的哪个Promise库,甚至是不同的Promise之间相互调用,都可以达到目的。


然而这个方案有一个不那么明显的缺陷,那就是会造成潜在的内存泄露。


试想,当你把回调函数传给Promise的then方法后,如果这时Promise的状态还没有确定下来,那么Promise实例肯定会在内部保留这些回调函数的引用;在一个robust的实现中,回调函数在执行完成后,Promise实例应该会释放掉这些回调函数的引用。如果使用上述方案,那么返回一个永远处于pending状态的Promise之后的Promise链上的所有Promise都将处于pending状态,这意味着后面所有的回调函数的内存将一直得不到释放。在简单的页面里使用这种方案也许还行得通,但在WebApp或者Node里,这种方案明显是不可接受的。


Promise.stop = function() {

  return new Promise(function(){})

}


doSth()

.then(value => {

  if (sthBigErrorOccured()) {

    return Promise.stop()

  }

  // normal logic

})

.catch(reason => {// this function will never got GCed

  // normal logic

})

.then()

.catch(reason => {// this function will never got GCed

  // normal logic

})

.then()

.catch(reason => {// this function will never got GCed

  // normal logic

})

那有没有办法即达到停止后面的链,同时又避免内存泄露呢。


让我们回到一开始的思路,我们在Promise链上所有的catch里都加上一句if,来判断传来的错误是否为一个无法处理的错误,如果是则一直往后面抛,这样就达到了即没有运行后面的逻辑,又避免了内存泄露的问题。


这是一个高度一致的逻辑,我们当然可以把它抽离出来。我们可以实现一个叫next的函数,挂在Promise.prototype上面,然后在里面判断是否是我们能处理的错误,如果是,则执行回调,如果不是,则一直往下传:


var BIG_ERROR = new Error('BIG_ERROR')


Promise.prototype.next = function(onResolved, onRejected) {

  return this.then(function(value) {

    if (value === BIG_ERROR) {

      return BIG_ERROR

    } else {

      return onResolved(value)

    }

  }, onRejected)

}


doSth()

.next(function(value) {

  if (sthBigErrorOccured()) {

    return BIG_ERROR

  }

  // normal logic

})

.next(value => {

  // will never get called

})

进一步,如果把上面代码中“致命错误”的语义换成“跳过后面所有的Promise”,我们就可以得到跳过后续Promise的方式了:


var STOP_SUBSEQUENT_PROMISE_CHAIN = new Error()


Promise.prototype.next = function(onResolved, onRejected) {

  return this.then(function(value) {

    if (value === STOP_SUBSEQUENT_PROMISE_CHAIN) {

      return STOP_SUBSEQUENT_PROMISE_CHAIN

    } else {

      return onResolved(value)

    }

  }, onRejected)

}


doSth()

.next(function(value) {

  if (sthBigErrorOccured()) {

    return STOP_SUBSEQUENT_PROMISE_CHAIN

  }

  // normal logic

})

.next(value => {

  // will never get called

})

为了更明显的语义,我们可以把“跳过后面所有的Promise”单独封装成一个Promise:


var STOP = {}

Promise.stop = function(){

  return Promise.resolve(STOP)

}


Promise.prototype.next = function(onResolved, onRejected) {

  return this.then(function(value) {

    if (value === STOP) {

      return STOP

    } else {

      return onResolved(value)

    }

  }, onRejected)

}


doSth()

.next(function(value) {

  if (sthBigErrorOccured()) {

    return Promise.stop()

  }

  // normal logic

})

.next(value => {

  // will never get called

})

这样就实现了在语义明确的情况下,不造成内存泄露,而且还停止了后面的Promise链。


为了对现有代码尽量少做改动,我们甚至可以不用新增next方法而是直接重写then:


(function() {

  var STOP_VALUE = Symbol()//构造一个Symbol以表达特殊的语义

  var STOPPER_PROMISE = Promise.resolve(STOP_VALUE)


  Promise.prototype._then = Promise.prototype.then


  Promise.stop = function() {

    return STOPPER_PROMISE//不是每次返回一个新的Promise,可以节省内存

  }


  Promise.prototype.then = function(onResolved, onRejected) {

    return this._then(function(value) {

      return value === STOP_VALUE ? STOP_VALUE : onResolved(value)

    }, onRejected)

  }

}())


Promise.resolve(8).then(v => {

  console.log(v)

  return 9

}).then(v => {

  console.log(v)

  return Promise.stop()//较为明确的语义

}).catch(function(){// will never called but will be GCed

  console.log('catch')

}).then(function(){// will never called but will be GCed

  console.log('then')

})

以上对then的重写并不会造成什么问题,闭包里的对象在外界是访问不到,外界也永远也无法构造出一个跟闭包里Symbol一样的对象,考虑到我们只需要构造一个外界无法“===”的对象,我们完全可以用一个Object来代替:


(function() {

  var STOP_VALUE = {}//只要外界无法“===”这个对象就可以了

  var STOPPER_PROMISE = Promise.resolve(STOP_VALUE)


  Promise.prototype._then = Promise.prototype.then


  Promise.stop = function() {

    return STOPPER_PROMISE//不是每次返回一个新的Promise,可以节省内存

  }


  Promise.prototype.then = function(onResolved, onRejected) {

    return this._then(function(value) {

      return value === STOP_VALUE ? STOP_VALUE : onResolved(value)

    }, onRejected)

  }

}())


Promise.resolve(8).then(v => {

  console.log(v)

  return 9

}).then(v => {

  console.log(v)

  return Promise.stop()//较为明确的语义

}).catch(function(){// will never called but will be GCed

  console.log('catch')

}).then(function(){// will never called but will be GCed

  console.log('then')

})

这个方案的另一个好处(好处之一是不会造成内存泄露)是可以让你非常平滑地(甚至是一次性的)从“返回一个永远pending的Promise”过度到这个方案,因为代码及其语义都基本没有变化。在之前,你可以定义一个Promise.stop()方法来返回一个永远pending的Promise;在之后,Promise.stop()返回一个外界无法得到的值,用以表达“跳过后面所有的Promise”,然后在我们重写的then方法里使用。


这样就解决了停止Promise链这样一个让人纠结的问题。


在考察了不同的Promise实现后,我发现Bluebird和浏览器原生Promise都可以在Promise.prototype上直接增加实例方法,但Q和$q(Angular)却不能这么做,具体要在哪个子对象的原型上加或者改方法我就没有深入研究了,但相信肯定是有办法的。


可是这篇文章如果到这里就结束的话,就显得太没有意思了~~


顺着上面的思路,我们甚至可以实现Promise链的多分支跳转。


我们知道,Promise链一般来说只支持双分支跳转。


按照Promise链的最佳写法实践,处理成功的回调只用then的第一个参数注册,错误处理的回调只使用catch来注册。这样在任意一个回调里,我们可以通过return或者throw(或者所返回Promise的最终状态的成功与否)跳转到最近的then或者catch回调里:


doSth()

.then(fn1)

.catch(fn2)

.catch(fn3)

.then(fn4)

.then(fn5)

.catch(fn6)

以上代码中,任意一个fn都只能选择往后跳到最近一then或者catch的回调里。


但在实际的使用的过程中,我发现双分支跳转有时满足不了我的需求。如果能在不破坏Promise标准的前提下让Promise实现多分支跳转,将会对复杂业务代码的可读性以及可维护性有相当程度的提升。


顺着上面的思路,我们可以在Promise上定义多个有语义的函数,在Promise.prototype上定义对应语义的实例方法,然后在实例方法中判断传来的值,然后根据条件来执行或者不执行该回调,当这么说肯定不太容易明白,我们来看代码分析:


(function() {

  var STOP = {}

  var STOP_PROMISE = Promise.resolve(STOP)

  var DONE = {}

  var WARN = {}

  var ERROR = {}

  var EXCEPTION = {}

  var PROMISE_PATCH = {}


  Promise.prototype._then = Promise.prototype.then//保存原本的then方法


  Promise.prototype.then = function(onResolved, onRejected) {

    return this._then(result => {

      if (result === STOP) {// 停掉后面的Promise链回调

        return result

      } else {

        return onResolved(result)

      }

    }, onRejected)

  }


  Promise.stop = function() {

    return STOP_PROMISE

  }


  Promise.done = function(value) {

    return Promise.resolve({

      flag: DONE,

      value,

    })

  }


  Promise.warn = function(value) {

    return Promise.resolve({

      flag: WARN,

      value,

    })

  }


  Promise.error = function(value) {

    return Promise.resolve({

      flag: ERROR,

      value,

    })

  }


  Promise.exception = function(value) {

    return Promise.resolve({

      flag: EXCEPTION,

      value,

    })

  }


  Promise.prototype.done = function(cb) {

    return this.then(result => {

      if (result && result.flag === DONE) {

        return cb(result.value)

      } else {

        return result

      }

    })

  }


  Promise.prototype.warn = function(cb) {

    return this.then(result => {

      if (result && result.flag === WARN) {

        return cb(result.value)

      } else {

        return result

      }

    })

  }


  Promise.prototype.error = function(cb) {

    return this.then(result => {

      if (result && result.flag === ERROR) {

        return cb(result.value)

      } else {

        return result

      }

    })

  }


  Promise.prototype.exception = function(cb) {

    return this.then(result => {

      if (result && result.flag === EXCEPTION) {

        return cb(result.value)

      } else {

        return result

      }

    })

  }

})()

然后我们可以像下面这样使用:


new Promise((resolve, reject) => {

    // resolve(Promise.stop())

    // resolve(Promise.done(1))

    // resolve(Promise.warn(2))

    // resolve(Promise.error(3))

    // resolve(Promise.exception(4))

  })

  .done(value => {

    console.log(value)

    return Promise.done(5)

  })

  .warn(value => {

    console.log('warn', value)

    return Promise.done(6)

  })

  .exception(value => {

    console.log(value)

    return Promise.warn(7)

  })

  .error(value => {

    console.log(value)

    return Promise.error(8)

  })

  .exception(value => {

    console.log(value)

    return

  })

  .done(value => {

    console.log(value)

    return Promise.warn(9)

  })

  .warn(value => {

    console.log(value)

  })

  .error(value => {

    console.log(value)

  })

以上代码中:


如果运行第一行被注释的代码,这段程序将没有任何输出,因为所有后面的链都被“停”掉了


如果运行第二行被注释的代码,将输出1 5 9


如果运行第三行被注释的代码,将输出2 6 9


如果运行第四行被注释的代码,将输出3 8


如果运行第五行被注释的代码,将输出4 7


即return Promise.done(value)将跳到最近的done回调里


依次类推。


这样就实现了Promise链的多分支跳转。针对不同的业务,可以封装出不同语义的静态方法和实例方法,实现任意多的分支跳转。


但这个方案目前有一点不足,就是不能用then来捕获任意分支:


new Promise((resolve) => {

  resolve(Promise.warn(2))

})

.then(value => {


})

.warn(value => {


})

这种写法中,从语义或者经验上讲,then应该捕获前面的任意值,然而经过前面的改动,这里的then将捕获到这样的对象:


{

  flag: WARN,

  value: 2

}

而不是2,看看前面的代码就明白了:


Promise.prototype.then = function(onResolved, onRejected) {

  return this._then(result => {

    if (result === STOP) {

      return result

    } else {

      return onResolved(result)// 将会走这条分支,而此时result还是被包裹的对象

    }

  }, onRejected)

}

目前我还没有找到比较好的方案,试了几种都不太理想(也许代码写丑一点可以实现,但我并不想这么做)。所以只能在用到多分支跳转时不用then来捕获传来的值。


不过从有语义的回调跳转到then是可以正常工作的:


doSth()

.warn()

.done()

.exception()

.then()

.then()

.catch()

同样还是可以根据上面的代码看出来。


最后,此文使用到的一个anti pattern是对原生对象做了更改,这在一般的开发中是不被推荐的,本文只是提供一个思路。在真正的工程中,可以继承Promise类以达到几乎相同的效果,此处不再熬述。


复制链接 网友评论 收藏本文 关闭此页
上一条: VUE.JS入门教程  下一条: 原生js添加和删除元素属性
夜鹰教程网成立于2008年,目前已经运营了将近 13 年,发布了大量关于 html5/css3/C#/asp.net/java/python/nodejs/mongodb/sql server/android/javascript/mysql/mvc/easyui/vue/echarts原创教程。 我们一直都在坚持的是:认证负责、一丝不苟、以工匠的精神来打磨每一套教程,让读者感受到作者的用心。我们默默投入的时间,确保每一套教程都是一件作品,而不是呆板的文字和视频! 目前我们推出在线辅导班试运营,模式为一对一辅导,教学工具为QQ。我们的辅导学科包括 java 、android原生开发、webapp开发、商城开发、C#和asp.net开发,winform和物联网开发、web前端开发,但不仅限于此。 普通班针对的是国内学员,例如想打好基础的大学生、想转行的有志青年、想深入学习的程序员、想开发软件的初学者或者业余爱好者等。 就业办针对即将毕业上岗的大四学生,或者打算转行的初级开发工程师。 留学生班针对的是在欧美、加拿大、澳洲、日本、韩国、新加坡等地留学的中国学子,目的是让大家熟练地掌握编程技能,按时完成老师布置的作业,并能顺利地通过考试。 详细咨询QQ:1416759661   夜鹰教程网  基于角色的权限管理系统(c-s/b-s)。
  夜鹰教程网  基于nodejs的聊天室开发视频教程
  夜鹰教程网  Git分布式版本管理视频教程
  夜鹰教程网  MVC+EasyUI视频教程
  夜鹰教程网  在线考试系统视频教程
  夜鹰教程网  MongoDB视频教程。
  夜鹰教程网 Canvas视频教程
  夜鹰教程网 报表开发视频教程
  推荐教程/优惠活动

  热门服务/教程目录

  夜鹰教程网  新手必看,详细又全面。
  夜鹰教程网  购买教程  夜鹰教程网  在线支付-方便
  夜鹰教程网  担保交易-快捷安全   夜鹰教程网  闪电发货
  夜鹰教程网  电话和QQ随时可以联系我们。
  夜鹰教程网 不会的功能都可以找我们,按工作量收费。

客服电话:153 9760 0032

购买教程QQ:1416759661  
  热点推荐
ajax 清除缓存的两种方法
js日历控件点击日期显示在文本框中…
HTML、JS与FLASH 之间的静态传值方…
主题:ajax请求JSP,为什么GET就是…
javascript 改变iframe(框架)的方…
javascript取鼠标当前坐标
推荐一款网页软键盘 很漂亮的哦
ajax session过期问题的几个解决方…
js文字间隔停顿向上滚动效果
ajax 服务器文本框自动填值
js技术技巧收藏(200例)---1
ajax 数据库中随机读取5条数据动态…
主题:这是否是个捷径?Ajax利用S…
揭开AJAX神秘的面纱(AJAX个人学习…
常用的JS后台导航菜单
  尊贵服务
夜鹰教程网 承接业务:软件开发 网站开发 网页设计 .Net+C#+VS2008+MSsql+Jquery+ExtJs全套高清完整版视频教程
  最近更新
js处理键盘事件(keydown event)…
Web前端技术疑点难点汇总
Asp.Net Core2.0允许跨域请求设置…
XMLHttpRequest请求中的跨域问题
原生js节点的操作 创建、添加、移…
VUE2.0组件:父组件子组件之间值的…
JavaScript是世界上最流行的脚本语…
js正则表达式表单验证详解
js正则表达式大全
详细且实用的JS正则表达式大全
EcmaScript5中扩展了叫bind的方法…
attachEvent和addEventListener的…
addEventListener的使用方式
通过构造器的方式来创建函数
为什么需要addEventListener
  工具下载  需要远程协助? 

sql2008视频教程 c#视频教程

VIP服务:如果您的某个功能不会做,可以加我们QQ,给你做DEMO!

JQUERY  Asp.net教程

MVC视频教程  vs2012
.NET+sql开发
手机:15397600032 C#视频教程下载
微信小程序 vue.js高级实例视频教程

教程咨询QQ:1416759661


这篇文章不能解决你的问题?我们还有相关视频教程云课堂 全套前端开发工程师培训课程

微信号:yyjcw10000 QQ:1416759661  远程协助需要加QQ!

业务范围:视频教程|程序开发|在线解答|Demo制作|远程调试| 点击查看相关的视频教程

技术范围:全端开发/前端开发/webapp/web服务/接口开发/单片机/C#/java/node/sql server/mysql/mongodb/android/。 



关于我们 | 购买教程 | 网站建设 | 技术辅导 | 常见问题 | 联系我们 | 友情链接

夜鹰教程网 版权所有 www.yyjcw.com All rights reserved 备案号:蜀ICP备08011740号3