-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
从如何停掉 Promise 链说起 #5
Comments
小菜来了,膜拜maya大神 |
还是 async/await 比较顺手 |
万能的 |
我们逻辑一般都是这样写的 |
不建议在每个promise对象后处理then和catch,promise链的最后使用catch处理错误即可,在链的then里判断有错时throw error就行(这样后面的then并不会执行),这样就可以停止promise链了吧 |
@Asher-Tan 对的,如果后面全是then的话,throw一下就可以了,但有时候后面即有then也有catch,或者有些then里有两个参数,这种方式就不太方便了 |
stop了整个Promise chain,最后要catch到这个stop的,做后续处理,你这个貌似没有考虑这一层。 |
.catch(Promise.stop)与.catch(e=>Promise.stop())是不一样的,仔细考虑:) |
奥,我表达有误,是想这个样子的 需要有whenstop((msg) => {//handle stop msg}) 我试了各种重写then的方式,无果,不知阁下有何见解 |
请问 有没有实际场景 测出内存泄漏的例子? |
其实很容易用代码测出来。 |
占个坑,感觉确实不错~ |
@xieranmaya 感觉那个内存泄露问题,设定个单例也是可以的 |
Q.fcall(function(){ |
写的太好了,层层深入,清晰易懂,点赞~! |
@Seven4X 多谢回复,不过感觉我们好像讨论的不是同一个问题,你的代码中并没有展示停掉Promise链的语义,建议你好好看看文章讨论的主题~ |
真·大牛 |
一直pending不会内存泄漏。 |
一直pending有可能会内存泄露, |
@toyang 这个得到的Promise最终确实会一直pending,但在它自身的Promise链条上,它后面是没有任何Promise的,所以也就不会有对某些函数的引用。 |
怎么用ts声明一个.d.ts的文件实现这样的过程,有人可以写一下吗 |
mark |
学习一下大佬的思维 |
通过一天的研究楼主思路,经过实验证明,取消调用裢,只需要return 一个 永远pedding状态的 promise即可,花里胡哨的封装stop的本质一模一样,关于gc问题,return new Promise(function() { }),会被gc掉。https://zhuanlan.zhihu.com/p/385764204?ivk_sa=1024320u |
在使用Promise处理一些复杂逻辑的过程中,我们有时候会想要在发生某种错误后就停止执行Promise链后面所有的代码。
然而Promise本身并没有提供这样的功能,一个操作,要么成功,要么失败,要么跳转到then里,要么跳转到catch里。
如果非要处理这种逻辑,一般的想法是抛出一个特殊的Error对象,然后在Promise链后面的所有catch回调里,检查传来的错误是否为该类型的错误,如果是,就一直往后抛,类似下面这样
这种方案的问题在于,你需要在每一个catch里多写一个if来判断这个特殊的Error,繁琐不说,还增加了耦合度以及重构的困难。
如果有什么办法能直接在发生这种错误后停止后面所有Promise链的执行,我们就不需要在每个catch里检测这种错误了,只需要编写处理该catch块本应处理的错误的代码就可以了。
有没有办法不在每个catch里做这种判断呢?
办法确实是有的,那就是在发生无法继续的错误后,直接返回一个始终不resolve也不reject的Promise,即这个Promise永远处于pending状态,那么后面的Promise链当然也就一直不会执行了,因为会一直等着。类似下面这样的代码
这种方案的好处在于你几乎不需要更改任何现有代码,而且兼容性也非常好,不管你使用的哪个Promise库,甚至是不同的Promise之间相互调用,都可以达到目的。
然而这个方案有一个不那么明显的缺陷,那就是会造成潜在的内存泄露。
试想,当你把回调函数传给Promise的then方法后,如果这时Promise的状态还没有确定下来,那么Promise实例肯定会在内部保留这些回调函数的引用;在一个robust的实现中,回调函数在执行完成后,Promise实例应该会释放掉这些回调函数的引用。如果使用上述方案,那么返回一个永远处于pending状态的Promise之后的Promise链上的所有Promise都将处于pending状态,这意味着后面所有的回调函数的内存将一直得不到释放。在简单的页面里使用这种方案也许还行得通,但在WebApp或者Node里,这种方案明显是不可接受的。
那有没有办法即达到停止后面的链,同时又避免内存泄露呢。
让我们回到一开始的思路,我们在Promise链上所有的catch里都加上一句if,来判断传来的错误是否为一个无法处理的错误,如果是则一直往后面抛,这样就达到了即没有运行后面的逻辑,又避免了内存泄露的问题。
这是一个高度一致的逻辑,我们当然可以把它抽离出来。我们可以实现一个叫next的函数,挂在Promise.prototype上面,然后在里面判断是否是我们能处理的错误,如果是,则执行回调,如果不是,则一直往下传:
进一步,如果把上面代码中“致命错误”的语义换成“跳过后面所有的Promise”,我们就可以得到跳过后续Promise的方式了:
为了更明显的语义,我们可以把“跳过后面所有的Promise”单独封装成一个Promise:
这样就实现了在语义明确的情况下,不造成内存泄露,而且还停止了后面的Promise链。
为了对现有代码尽量少做改动,我们甚至可以不用新增next方法而是直接重写then:
以上对then的重写并不会造成什么问题,闭包里的对象在外界是访问不到,外界也永远也无法构造出一个跟闭包里Symbol一样的对象,考虑到我们只需要构造一个外界无法“===”的对象,我们完全可以用一个Object来代替:
这个方案的另一个好处(好处之一是不会造成内存泄露)是可以让你非常平滑地(甚至是一次性的)从“返回一个永远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回调里:
以上代码中,任意一个fn都只能选择往后跳到最近一then或者catch的回调里。
但在实际的使用的过程中,我发现双分支跳转有时满足不了我的需求。如果能在不破坏Promise标准的前提下让Promise实现多分支跳转,将会对复杂业务代码的可读性以及可维护性有相当程度的提升。
顺着上面的思路,我们可以在Promise上定义多个有语义的函数,在Promise.prototype上定义对应语义的实例方法,然后在实例方法中判断传来的值,然后根据条件来执行或者不执行该回调,当这么说肯定不太容易明白,我们来看代码分析:
然后我们可以像下面这样使用:
以上代码中:
即return Promise.done(value)将跳到最近的done回调里
依次类推。
这样就实现了Promise链的多分支跳转。针对不同的业务,可以封装出不同语义的静态方法和实例方法,实现任意多的分支跳转。
但这个方案目前有一点不足,就是不能用then来捕获任意分支:
这种写法中,从语义或者经验上讲,then应该捕获前面的任意值,然而经过前面的改动,这里的then将捕获到这样的对象:
而不是
2
,看看前面的代码就明白了:目前我还没有找到比较好的方案,试了几种都不太理想(也许代码写丑一点可以实现,但我并不想这么做)。所以只能在用到多分支跳转时不用then来捕获传来的值。
不过从有语义的回调跳转到then是可以正常工作的:
同样还是可以根据上面的代码看出来。
最后,此文使用到的一个anti pattern是对原生对象做了更改,这在一般的开发中是不被推荐的,本文只是提供一个思路。在真正的工程中,可以继承Promise类以达到几乎相同的效果,此处不再熬述。
多谢各位同僚的阅读,如有纰漏之处还请留言指正~
The text was updated successfully, but these errors were encountered: