专栏首页程序人生Promise: 给我一个承诺,我还你一个承诺

Promise: 给我一个承诺,我还你一个承诺

处理concurrent programming,除了threading/multi-processing外,各家语言都有自己的绝活:erlang/elixir是actor model,golang/clojure(core.async)是CSP,haskell/clojure是STM,而javascript是event loop/callback。

callback可能是这几种并发模型里面最好懂的一种方式,就像好莱坞经纪人惯常的做法:don’t call me, I’ll call you back。比如打开数据库,打开要访问的表,写入一列新的数据这样一系列IO密集型的操作,如果同步去做,等待的时间要远大于运算的时间,而使用callback异步处理则消除了等待,大大增强了软件的并行性。然而,callback理解起来很直观,写起代码来很费劲,稍微复杂一些的处理,很容易搞成下图这样的pyramid of doom,也就是俗称的callback hell:

当然,你可以通过重构,把严重嵌套,影响阅读的pyramid拆分成若干个小的pyramid,减少眼睛出血(eye-bleeding)的概率,但毕竟治标不治本。于是,在各种版本的第三方javascript类库里,大家都实现了各自的Promise/A+对象,来减少对callback的依赖。

Promise是这样一个对象,对于任意的异步操作,它提供了一组固定的API,来操作这个结果。我们先看一段代码:

我们看到,如果要把一个异步操作封装成Promise,我们需要首先创建一个Promise,并提供一个包含两个参数 resolvereject的函数,在这个函数里调用你的异步方法(这里用setTimeout模拟)。如果异步方法成功,则在其callback里面调用 resolve,提供成功后获得的数据;如果失败,则调用 reject,提供错误数据。这一般是类库提供者(producer)要做的事情。

对于类库调用者(consumer),拿到一个Promise对象,他可以调用 then 方法来获取异步后的数据,也可以调用 catch 来处理错误。Promise提供了如下机制来简化consumer的代码:

  • then 依旧返回一个Promise,这样,代码的撰写由视觉上的横向延伸(callback hell)变成纵向扩展(chained operation),可读性增强
  • error propagation,在若干个Promise间不断chain的过程中,期间发生的任何error都会被一路传递到最后的Promise的 reject,方便程序员用一个 catch 捕获一条链上的错误,同样的,可读性大大增强

我们看之前那个callback-hell使用Promise撰写后的代码:

代码清晰了不少。下面是Promise处理的状态机:

在ES5,Promise并非原生支持,但有很多第三方的类库支持;在ES6中,Promise形成了一个标准,并且在语言层面原生支持。

Promise在实际使用中除了解决callback hell,让代码可读性增强外,还可以做很多事情。因为Promise实际上可以被视作一个Monad,所以你可以将其用在很多本来难以做composition的场合。比如你有一个处理,需要依赖多个数据源,他们或同步(数据已经在内存中直接可读),或异步(数据需要从数据库或者文件系统读取,甚至来自第三方API),正常来说似乎很难被抽象成一个数据结构。然而,你可以将这些数据源统统封装成Promise(同步的数据可以被视作一个状态立即走到resolved的Promise),这样,可以统一处理。比如说 Promise.all(iterable)(resolve所有结果,返回新的Promise),或者 Promise.race(iterable)(只要有一个结果resolve出来,就立即返回新的Promise,典型的anycast使用场景)。

目前nodejs的库函数还是callback方式,虽说手工转换成Promise非常简单,但毕竟不那么方便。在nodejs app里,你可以使用bluebird(或者es6-promisify)来批量转化nodejs的标准库。比如:

可以被转化成如下的代码:

最后,说说Promise的缺点。

第一,一个Promise只能resolve单个数据,对应于同步处理里的单值数据;如果要处理异步场景下的 "array",那么,Observable是更好的方式。

第二,Promise的API设计感觉有些缺陷,并非lazy(可能是历史原因,也可能考虑到API友好程度),一旦启动,不可终止。如果你需要处理可终止的异步操作,那么,也需要使用Observable。下面是Promise和Observable的代码的对比,可以看到,一旦创建,Promise会立刻执行对象体内的代码(不管你有没有调用 then),而Observable直到subscriber真正要读取时(forEach)才会求值,而且,Observable提供了cancel的API:

即便Oberservable已经开始运行,只要还未完成,调用者都有机会种植它。

OK,今天就先讲到这里,以后我们再讲Observable。

本文分享自微信公众号 - 程序人生(programmer_life),作者:Tyr

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2015-09-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 银河系跳槽指南

    这几天跟朋友聊的很多的一个话题是跳槽。马老师说员工离职的原因林林总总,只有两点最真实:1. 钱,没给到位;2. 心,委屈了。两个理由合二为一,就是干得不爽。如果...

    tyrchen
  • 压力管理续:人比人气死人

    今早七点起一早的会,真让人难以为继啊。 昨天的文章收到了好几十条回复,差点看不过来,最有趣的几条是: 东莞曾经是减压圣地,现在没了 『医生的处方白纸黑丝的摆在那...

    tyrchen
  • 异步处理的脑力游戏

    用过 node.js 的同学都知道,它实现了 Observer 设计模式,做了一套类似于 Python 的 event listener,叫 EventEmit...

    tyrchen
  • Promise的三兄弟:all(), race()以及allSettled()

    从ES6 开始,我们大都使用的是 Promise.all()和Promise.race(),Promise.allSettled() 提案已经到第4阶段,因此将...

    Fundebug
  • es6 Promise

    Promise 是异步编程的一种方案,简单说就是一个容器,里面保存着某个未来才会结束的事件的 结果,Promise 是一个对象,从它,可以获取异步操作的消息。 ...

    用户1197315
  • 你不知道的JavaScript(中卷)二

    1.最常见的块单位是函数。从现在到将来的“等待”,最简单的方法(但绝不是唯一的,甚至也不是最好的)是使用一个通常称为回调函数的函数

    硬核项目经理
  • 你真的懂Promise吗

    在异步编程中,Promise 扮演了举足轻重的角色,比传统的解决方案(回调函数和事件)更合理和更强大。可能有些小伙伴会有这样的疑问:2020年了,怎么还在谈论P...

    浪里行舟
  • web前端面试题对答篇一:谈谈你对Promise的理解

    回答这个问题时,个人不建议单纯的从Promise的细节知识点答起,因为这个问题的本质是拥有一定宏观性的,如果仅仅回复一些知识点恐怕是满足不了面试官胃口的。

    用户1272076
  • Salesforce Javascript(一) Promise 浅谈

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects...

    用户1169343
  • 当面试官问你Promise的时候,他究竟想听到什么?

    用户1687375

扫码关注云+社区

领取腾讯云代金券