在看angularjs 和 dojo中文档时总会看到Promise 和 Deferred,今天在google 在找到两篇关于这方面的文章。
介绍
不久以前,Javascript 程序员来处理异步事件时主要使用回调函数。
回调函数是一段执行代码,作为参数传递给另外一段执行函数,在适合的时候执行,而不是在调用时立即执行 - Wikipedie
也就是说, 一个函数可以作为参数传递给另外一个函数,当另外一个函数被调用时,它开始执行。
在回调函数内部没有任何错误, 但根据其环境,我们在编写程序时有更多的方法来管理异步事件, 在这里我们的目标要测试一组可用的方法:promise 对像和 deferred对像。 在第一部分,我们将讲理论和语法,在第二部分讲解实际使用。
在Javascript 有效使用异步事件的关键是理解程序的继续执行(获得想要的值继续执行),即使在进行中没有获得想要的值(出现网络错误或者其它)也能继续执行相应代码。 在Javascript异步事件中处理还未完成的任务(还在请求,不知道会返回什么值)的值非常有挑战 - 特别是当你第一次开始时。
最经典的例子是 XMLHttpRequest( Ajax), 想像下你想做的:
- 创建一个 ajax 请求,来获得一些数据
- 立刻用这些数据来做某件事件 然后
- 做另外一件事件
在我们的程序中,先初始化一个Ajax 请求。 提出请求后不像同步事件那样暂停运行,而是继续执行。 等到我们从Ajax request 获得响应数据,整个应用程序都完成了运行。
承诺(Promises) & 延迟(Deferreds): 是什么?
Promises是一种编辑结构,1976以来一直存在。 简而言之:
从一个更高水平考虑, 在Javascript中的承诺让我们可以以同步的方式来输写异步代码。
Promise/A
Promise/A 建议了以下标准行为但没有规定API实现的细节.
一个Promise:
- 代表单次完成操作返回的最终值
- 可以是以下3种状态之一: 未实现的,实现的 和 失败的。 仅允许 未实现状态到实现状态或者失败状态。
- 具有一个函数作为属性 then的值。 then会返回一个promise.
- 为了在承诺结束时,需要添加 fulfilledHanlder, errorHandler和progressHandler。
- 从回调处理函数返回的值是返回的承诺的fulfillment值。
- 承诺的值不准被改变(避免监听器创建意外行为时产生副作用)
换句话说,不管这些细微的差别:
一个Promise(承诺)充当一个末来值的代理, 它有3种可能的状态,并且需要添一个函数, 这个函数添加了多个处理器来处理它的状态:fulfilledHandler, errorHandler 和progressHandler(可选) 并且当处理器执行完毕后会返回一个被解析或者被拒绝的Promise.
状态和承诺返回值
一个承诺可能含有3种状态: unfulfilled, fulfilled 和 failed.
- unfulfilled: 由于一个承诺是未知值的代理,所以它开始时为 unfulfilled 状态。
- fulfilled: 承诺被它想要的值填充
- failed: 如果承诺被返回为一个异常, 则为 failed状态。
一个承诺只能从unfulfilled 到 fulfilled或者 failed. 根据解决(获得想要的值)或者拒决 状态, 任何观察者会被通知并且将promise/value的值传递给观察者。 一旦 promise已被解析(获得值)或者 被拒绝,状态和结果者都不能在修改。
这里有一个例子:
// Promise to be filled with future value
var futureValue = new Promise();
// .then() will return a new promise
var anotherFutureValue = futureValue.then();
// Promise state handlers ( must be a function ).
// The returned value of the fulfilled / failed handler will be the value of the promise.
futureValue.then({
// Called if/when the promise is fulfilled
fulfilledHandler: function() {},
// Called if/when the promise fails
errorHandler: function() {},
// Called for progress events (not all implementations of promises have this)
progressHandler: function() {}
});
不同的实现和性能
当选择一个promise库时, 有一些事项里需要考虑。 并非所有的实现都是一样的, 它们在提供的API工具,性能, 行为都有不同。
由于Promise/A 标准 仅是一个promises 行为的概括。 而没有实现的细节。 不同的pormise库有不同的功能集。 所有遵循 Promise/A标准的库都有 .then()函数, 但它们的API还是不同。 另外, 它们依然可以彼此交换promises(一个库中的承诺可在另一个库里使用)。 jQuery比较例外,因为它没有完全实现Promise/A. 关于它的决定可以查看 文档 和 讨论. 在 jQuery实现一个未捕获异常来停止程序的运行。 由于不同的实现, 它跟实现了Promise/A标准的库一起工作时就会出现问题。
一个解决方案就是将jQuery的承诺转化到遵循 Promise/A 的库。 然后在使用遵循 Promise/A 的API
例如:
when($.ajax()).then()
当全部阅读完整篇关于jQuery决定依然采用自己的promise. 一提到性能就是激起了我的好奇心。 我决定做一个快速的性能测试, 我使用了Benchmark.js 和 测试创建的结果 并且解析了一个 deferred对像。
以下是结果:
| jQuery 91.6kb |
When.js 1.04kb |
Q.js 8.74kb |
| 9,979 ops/sec ±10.22% |
96,225 ops/sec ±10.10% |
2,385 ops/sec ±3.42% |
注:通过 Closure Compiler压缩,但没有gzipped过。
在运行完它们的测试,我从 深入测试promise 库的文章里发现了相似的结果。
不同的性能在真实的应用里微不足道。但在我的Benchmark.js测试中, when.js 以明显优势胜出。
除了大小和性能,显然还有其它的考虑。 应该使用哪个库,还取决于你特定的情况和项目的需要。 以下是一些库的概述:
- when.js :快速, 轻量的实现, 包含许多有用的工具。 在2.0版本完全支持异步解决方案。
- Q.js: 可以在浏览器和 Node.js上运行。 提供了一个强键的API, 完全遵循 Promise/A。
- RSVP: 裸机实现,完全支持Promise/A。
- jQuery: 不遵循Pomise/A, 但被广泛使用。 如果你已经在项目中使用了jQuery, 它很容易上手。 值得看看。
总结
承诺为Javascript开发者提供了一个处理异步事件的工具。 现在我们已经讲了promise对像和 deferred对像的细节以及表现。 我们准备进一步来看看如何使用它们。 在第二部分, 我们将近距离的使用Promises, 一些常见的问题和Jquery API实现的细节。
资源: