V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
ysmood
V2EX  ›  分享创造

由于 Bluebird 体积太大,重造了一个 Promise 库

  •  
  •   ysmood ·
    ysmood · 2015-05-17 17:10:42 +08:00 · 5598 次点击
    这是一个创建于 3510 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目地址:yaku

    由于一些轻型项目只需要使用最基本 Promise 功能,比如移动端,而现有的库不是太大就是功能不够完善,比如我向这个库提的这个 issue 无法被采纳。

    这个库浏览器支持到 IE5+,体积只有 3.2KB,无任何依赖,比其他同类库小很多(Bluebird / 73KB, ES6-promise / 18KB)。对性能和异常处理做了优化。单测覆盖率 100%。如果你想学习 Promise 细节,这个库会是个不错的起点,我专门为此写了一个仅 80 行的实现,更多细节可以去看看 readme。

    也是看了这个帖子,有感而发。我就埋头做事,不发表任何评论。

    35 条回复    2015-05-21 08:43:46 +08:00
    leecade
        1
    leecade  
       2015-05-17 19:08:59 +08:00
    敢用
    magicdawn
        2
    magicdawn  
       2015-05-17 19:33:18 +08:00
    深入浅出node.js上有教简单实现!
    ysmood
        3
    ysmood  
    OP
       2015-05-17 19:40:25 +08:00
    @magicdawn 我这个跟书上的不一样,一般书上的实现是跑不过那 800 多个单测的,我这个 80 行的是可以的,我这个可能更偏向工程实际些。对于想更深入了解的人可能有帮助。
    laoyur
        4
    laoyur  
       2015-05-17 19:44:38 +08:00 via Android
    mark,关注下
    楼主能贴一下跟bluebird的性能比较就更好了
    ysmood
        5
    ysmood  
    OP
       2015-05-17 19:59:04 +08:00
    @laoyur readme 里已经有个和各个库的性能比较了,看那个表格,其中有 Bluebird,你甚至能自己跑下 benchmark,具体怎么运行 readme 里也有。不过更复杂情况的性能测试不是当前的开发重点,性能会之后慢慢优化。
    otakustay
        6
    otakustay  
       2015-05-17 21:05:42 +08:00
    我们也撸了一个Promise库,与楼主共同分享相互学习下

    https://github.com/ecomfe/promise

    测试都是过Promise/A+的一个样,unhandledRejection错误处理也有,大小上大概这样

    ~ coffee -c -p yaku/src/yaku.coffee | uglifyjs | gzip -c | wc -c
    2064

    ~ cat promise/src/*.js | uglifyjs | gzip -c | wc -c
    2754

    我们的库加了2个功能,第一是enhance.js增加了一些便携的方法,比如thenBind、thenGetProperty。第二是setImmediate作了setImmediate函数的shim来让异步更快,去掉这2个的话

    ~ cat promise/src//Promise.js promise/src//PromiseCapacity.js promise/src/main.js promise/src/then.js promise/src/util.js promise/src/hook.js | uglifyjs | gzip -c | wc -c
    1799

    我们现在在考虑的是:

    1. 这样的库与原生的Promise的关系如何处理
    2. 全局错误处理应该怎么做
    3. Promise的cancel/abort应该怎么做

    与楼主共同学习
    ysmood
        7
    ysmood  
    OP
       2015-05-17 22:11:13 +08:00
    @otakustay 我这边除了 unhandled rejection,还有 long stack trace,这个也非常重要。另外你可以测下你的性能和我的差别。

    我的这个库的目的就是不添加除了调试用的任何非原生接口,换句话说用我这个库,可以没有任何副作用的删除它,切换到使用原生的 Promise。

    关于 cancel/abort 这个不是 Promise 应该解决的问题,ES6 的 community 已经应该讨论过这个问题了。
    ysmood
        8
    ysmood  
    OP
       2015-05-17 22:24:55 +08:00
    @otakustay 另外你理解的 unhandled reject 似乎不对。比如我用你的库运行

    Promise.reject(10).catch(function() {})

    结果还是会打印 10,这不符合 ES6 spec 的规范。
    otakustay
        9
    otakustay  
       2015-05-17 22:50:16 +08:00
    @ysmood

    我特地去找了一下,unhandled reject在ES6中还没有定义,我想不同的人会有不同的理解,跟着BlueBird走可能是个不错的选择,但最终会如何暂时还不知道。我们确实使用了更为同步的方式略过reject到catch的这个过程,这是我们从性能上考虑的设计了

    cancel/abort的问题现在还在讨论中,并没有被废弃吧,在ES7中会不会实现我是很期待的,事实上除开Promise,几乎各种语言中的异步库或者协程功能都会涉及到cancellation的功能,早晚都是逃不开的话题,特别是在SPA会越来越大行其道的当前前端环境下,没有cancellation功能的异步会很不好用

    性能上我试了下,其实你的性能应该是劣势的,特别在没有setImmediate的环境下做密集测试,但不同机器也可能会有不同的结果……

    最后,目的其实不一样,我们更希望除了shim之外,可以再做一些什么方便基于Promise的开发,如ES6范围内的Promise没有finally的功能,这是一个很大的缺憾,让不少的代码变成.then(foo, foo)并且不能使用匿名函数,略不方便。因此也拿出来和楼主的共同学习参考
    otakustay
        10
    otakustay  
       2015-05-17 22:55:36 +08:00
    @ysmood long stack trace确实很重要,我们也去研究下怎么支持……
    otakustay
        11
    otakustay  
       2015-05-17 23:11:59 +08:00
    @ysmood 我转了一圈回来,感觉在Chrome上支持async stack是很有必要的,但Node上的long stack我打算放弃,如果一个库的定位是原生Promise的shim的话,node 0.12已经自带Promise了,对于这种版本可控的环境来说我们就不打算再继续为其提供shim了

    所以还是和你的思路有差异化:)
    ysmood
        12
    ysmood  
    OP
       2015-05-17 23:16:56 +08:00
    @laoyur 所以这里为了问题的简化,只讨论 ES6,unhandled reject 这个我口误,是要符合现行 chrome 或者 firefox的处理方式,他们会在 gc 里处理它,我这边尽量模拟了这个过程,这很关键。

    关于性能,我帮你跑了下,在 node 下差距很大:

    Node v1.8.1
    OS darwin
    Arch x64
    CPU Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz
    --------------------------------------------------------------------------------
    yaku
    total: 324ms
    init: 215ms
    resolution: 109ms
    memory: rss - 115mb | heapTotal - 96mb | heapUsed - 76mb

    pp
    total: 2378ms
    init: 1940ms
    resolution: 438ms
    memory: rss - 300mb | heapTotal - 278mb | heapUsed - 252mb

    浏览器里没必要测性能,只有服务端 server 用 yaku 的时候这才是瓶颈,用户的 browser 里不可能跑重运算的。
    ysmood
        13
    ysmood  
    OP
       2015-05-17 23:23:48 +08:00
    @laoyur 除了 shim 外的功能应该基于一个基础库开发。比如我会把 yaku 写到完善,然后基于它写个 yaku-ex 之类的,人们可以选择最适合需求的,而不是一股脑都放一起。

    关于类似 finally 这种我很早就在 stackoverflow 上讨论过了,ES6 大概不会支持的见: http://stackoverflow.com/questions/26667598/will-javascript-es6-promise-support-done-api

    你可以很容易在类似 yaku-ex 这样的库里实现一个。
    ysmood
        14
    ysmood  
    OP
       2015-05-17 23:30:38 +08:00
    @laoyur 你这个在 node 下已经慢到跟 q(2755ms) 差不多了,Bluebird 最快 301ms,上面的测试越小越快。
    ysmood
        15
    ysmood  
    OP
       2015-05-17 23:40:57 +08:00
    @laoyur 而且类似这种单测也和原生 Promise 都不一致:

    new Promise(function () { throw 10 }).catch(function() {})

    其实不是把 promises-aplus-tests 都跑过就万事大吉了,还是有很多浏览器的规范在里面的。
    ysmood
        16
    ysmood  
    OP
       2015-05-17 23:46:20 +08:00
    抱歉 @laoyur 君,@错对象了,之前都是 @otakustay 才对。
    phoenixlzx
        17
    phoenixlzx  
       2015-05-17 23:48:08 +08:00   ❤️ 1
    给 ys 菊苣点赞

    star 已送
    otakustay
        18
    otakustay  
       2015-05-18 00:08:04 +08:00
    @ysmood 从一开始就没有打算让这样的Promise库在node中跑,所以node的性能对我的意义不大,原因很简单,node现在支持原生Promise所以没有使用的场景,我还是不怎么理解什么时候能在node中使用一个Promise shim库,因为node无法升级使用较新版本吗,无法使用较新node的时候又能引入一个第三方库吗……在我厂似乎还真没有这样的情况(考虑到node可以直接把binary做portable带上)

    关于finally的支持你可以去esdiscuss.org找,基本上会在ES7来添加这个API,纯粹因为讨论时ES6已经进入RC没办法再添加方法了

    确实我相信还存在一些(不少)的边界场景有BUG,也必然会不断跟进,多谢你提供的case,如果你有更全面的case不如我们合作来补充aplus-test?
    ysmood
        19
    ysmood  
    OP
       2015-05-18 00:19:03 +08:00
    @otakustay 我这个库就在你厂的一些项目跑着呢。我 readme 第一句话就是说比原生的快,这就是 node 使用它的理由。比如此时此刻你厂用的一个门户级网站就是就是用的 node 0.8,你部门不代表你厂,不代表现实复杂世界吧?
    otakustay
        20
    otakustay  
       2015-05-18 00:23:50 +08:00
    @ysmood 我当然理解,同时我们的库也以支持我们所能触及的应用为优先考虑呗……

    话说我从来没有否定或者贬低你的东西的应用场景和优秀性吧……
    ysmood
        21
    ysmood  
    OP
       2015-05-18 00:39:37 +08:00
    @otakustay 抱歉,话说的可能重了点,就是论事而已。

    题外话,可以试着自动生成文档。打个硬广吧,可以用我写的 nokit 自动生成文档,yaku就是用它生成的 readme。好玩的是 nokit 也依赖了 yaku。nokit 这个用于替代 gulp,更好用。看看 yaku 怎么用 nokit 的就行了。
    otakustay
        22
    otakustay  
       2015-05-18 00:43:10 +08:00
    @ysmood 这个好,nokit以前确实没听过,去看看

    老实说,有兴趣的话,真的一起补一下aplus-test吧,那东西有多不全其实我们都是知道的,更别说这货在浏览器里跑用上browserify也会有点小问题,被折腾了蛮久……
    ysmood
        23
    ysmood  
    OP
       2015-05-18 01:13:58 +08:00
    @otakustay 我不打算入 aplus-test 的坑了,我另有计划,我打算把 Bluebird 的 test suite 抽离出来,它的单测应该是业界比较可信的,是实践的结果。
    coolicer
        24
    coolicer  
       2015-05-18 08:23:28 +08:00
    es6 原生自带的啊
    ysmood
        25
    ysmood  
    OP
       2015-05-18 10:13:01 +08:00 via Android
    @coolicer 但当下并不是所有浏览器都实现了ES6
    coolicer
        26
    coolicer  
       2015-05-18 10:58:52 +08:00
    @ysmood 使用babeljs之后的,可以转成es5,眼下大部分浏览器支持es5
    coolicer
        27
    coolicer  
       2015-05-18 10:59:11 +08:00
    打错字,之类,不是之后。
    iwege
        28
    iwege  
       2015-05-18 11:09:22 +08:00
    bluebird 对catch的加强实际用起来的时候很赞。另外建议helper 可以做插件用户自己require。
    ysmood
        29
    ysmood  
    OP
       2015-05-18 11:49:02 +08:00
    @coolicer 我读过 babeljs 用的 core-js 来实现 Promise,这个实现很大,我刚测了最新的 babel,编译之后 minify 了也有 20KB,太大了,而且也不支持 unhandled rejection 和 long stack trace。性能也没看到有太多优化:

    Node v1.8.1
    OS darwin
    Arch x64
    CPU Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
    --------------------------------------------------------------------------------
    yaku
    total: 234ms
    init: 155ms
    resolution: 79ms
    memory: rss - 115mb | heapTotal - 96mb | heapUsed - 76mb
    babel-runtime
    total: 770ms
    init: 527ms
    resolution: 243ms
    memory: rss - 190mb | heapTotal - 172mb | heapUsed - 150mb

    比 yaku 慢了接近 3 倍
    ysmood
        30
    ysmood  
    OP
       2015-05-18 11:53:59 +08:00
    @iwege 对 catch 已经有所加强了,但还有很多可优化空间,之后有时间会进一步加强。helper 这个我不是很理解,能具体解释下吗?
    iwege
        31
    iwege  
       2015-05-18 12:43:46 +08:00
    @ysmood
    API 模块化,类似lodash,想要用到哪个就用添加哪个。

    Bluebird的catch是可以针对特定类型Error进行相关的处理,这个的方法比较简单也避免了自己去写if else的判断,代码可读性比不用要高很多。
    coolicer
        32
    coolicer  
       2015-05-18 13:25:31 +08:00
    重新看了帖子,项目还是好。能不能换个名字,让它跟promise沾边,我个人很怕用那些奇怪名字,因为不认识。像bluebird,起码也是个单词,好吧,都是无关重要的观点。
    ysmood
        33
    ysmood  
    OP
       2015-05-18 13:27:14 +08:00
    @lwege 了解了,可以加到扩展里去,但基础库不会有这个功能。当时我就是被 bluebird error 的这层 wrap 恶心到过,才想自己写个库的,它这么干会和原生写法不兼容。

    要扩展加个 xcatch 之类的就行了,比如你 new error 的时候,用库提供的些标准 error 类型,这时就能用 xcatch 到指定 type 的 error。

    关于 lodash 模块话 API 这个确实可以做,多谢提醒~
    ysmood
        34
    ysmood  
    OP
       2015-05-18 20:03:43 +08:00
    @coolicer 一开始就在 FAQ 里说了名字的问题,就是怕这样的吐槽,这名字其实就是 promise 的意思,只是换了一个语言而已,否则取名字太困难了,好的名字都被别人抢了。
    coolicer
        35
    coolicer  
       2015-05-21 08:43:46 +08:00
    老是跟youku联想起来 = =
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2961 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 14:14 · PVG 22:14 · LAX 06:14 · JFK 09:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.