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,并提供一个包含两个参数 resolve
,reject
的函数,在这个函数里调用你的异步方法(这里用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。
- OpenCV和SVM分类器在自动驾驶中的车辆检测
- 自动驾驶的模型预测控制
- 【专业技术】使用html5的十大原因
- 第五课:推理结果的可视化
- 第四课:模型的使用
- 【Java概念学习】--数组的初始化
- linux下重命名文件或文件夹使用mv既可实现。
- 第三课:把tensorflow,模型和测试数据导入Android工程
- D-Link 路由器信息泄露和远程命令执行漏洞分析及全球数据分析报告
- Wordpress安全架构分析
- CVE-2017-5123 漏洞利用全攻略
- 简单分析shared pool(三) (r5笔记第94天)
- OpenCV在车道线查找中的使用
- ESP32 DevKitC 编译烧写 AliOS Things
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- php7下的filesize函数
- PHP-FPM 设置多pool及配置文件重写操作示例
- laravel实现登录时监听事件,添加登录用户的记录方法
- php更新cookie内容的详细方法
- php实现映射操作实例详解
- Laravel 已登陆用户再次查看登陆页面的自动跳转设置方法
- yii框架数据库关联查询操作示例
- laravel-admin的多级联动方法
- Laravel数据库读写分离配置的方法
- php给数组赋值的实例方法
- php实现分页功能的详细实例方法
- 浅谈Laravel中的三种中间件的作用
- laravel 使用auth编写登录的方法
- laravel框架 laravel-admin上传图片到oss的方法
- php实现推荐功能的简单实例