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)

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

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏有趣的django

1.python简介

简介 1、python语言介绍 python的创始人:Guido Van Rossum 2、python是一门什么样的语言 编程语言主要从以下几个角度进行分类:...

41150
来自专栏我的技术专栏

Java多线程编程—锁优化

15970
来自专栏编程心路

Java 动态代理,看这篇就够了

这篇文章需要用到 Java 的反射知识,如果对反射还不清楚的小伙伴,可以先移步到这里 《Java进阶-反射》。

19340
来自专栏用户2442861的专栏

Python标准库11 时间与日期 (time, datetime包)

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢!

8920
来自专栏葡萄城控件技术团队

C#开发人员应该知道的13件事情

本文讲述了C#开发人员应该了解到的13件事情,希望对C#开发人员有所帮助。 1. 开发过程 开发过程是错误和缺陷开始的地方。使用工具可以帮助你在发布之后,解决掉...

24490
来自专栏从零开始学 Web 前端

嵌入式面试题(一)

4. 空指针(null pointer)指向了内存的什么地方(空指针的内部实现)?

12820
来自专栏新智元

Go 2.0发布在即,程序员有太多话要说

Go语言的开发者正着手准备开发2.0版本,并从以下三个方面发布了初步的设计方案(非官方正式版),以供社区开展讨论:

83510
来自专栏C语言及其他语言

【编程经验】C语言程序真正的启动函数(文末有福利,第二弹)

又到了小编和大家说到的时间了,闲话少说,直接上正题,今天就讲讲“C语言程序真正的启动函数”。 为什么要用”真正”这个词?因为我们从...

334120
来自专栏奇点大数据

用python做科学计算之pandas入门简介

pandas是一个开源的python数据分析和处理包,使用灵活方便,性能高,速度快,简单介绍一下它里面比较常用的功能 数据读取 它支持多种数据读取的方式这里简...

29860
来自专栏人工智能

机器学习如何从 Python 2 迁移到 Python 3

关键时刻,第一时间送达! ? 本文经授权转自人工智能头条。 Python 已经成为机器学习及其他科学领域中的主流语言。它不但与多种深度学习框架兼容,而且还包含优...

32660

扫码关注云+社区

领取腾讯云代金券