一个很重要的好处是,可以把这个事件侦听对象提供给代码中多个独立的部分;在 foo(…) 完成的时候,它们都可以独立地得到通知,以执行下一步:
var evt = foo( 42 );
// 让bar(..)侦听foo(..)的完成 bar( evt );
// 并且让baz(..)侦听foo(..)的完成 baz( evt );
对控制反转的恢复实现了更好的关注点分离,其中 bar(…) 和 baz(…) 不需要牵扯到 foo(…) 的调用细节。类似地,foo(…) 不需要知道或关注 bar(…) 和 baz(…) 是否存在, 或者是否在等待 foo(…) 的完成通知。
从本质上说,evt 对象就是分离的关注点之间一个中立的第三方协商机制。 Promise“事件”
你可能已经猜到,事件侦听对象 evt 就是 Promise 的一个模拟。
在基于 Promise 的方法中,前面的代码片段会让 foo(…) 创建并返回一个 Promise 实例,而 且这个 Promise 会被传递到 bar(…) 和 baz(…)。
我们侦听的 Promise 决议“事件”严格说来并不算是事件(尽管它们实现目 标的行为方式确实很像事件),通常也不叫作 “completion” 或 “error”。事 实上,我们通过 then(…) 注册一个 “then” 事件。或者可能更精确地说, then(…) 注册 “fullfillment” 和 / 或 “rejection” 事件,尽管我们并不会 在代码中直接使用这些术语。
考虑:
function foo(x) {
// 开始做一些可能耗时的工作
// 构造并返回一个promise
return new Promise( function(resolve,reject){
// 最终调用resolve(..)或者reject(..)
// 这是这个promise的决议回调 } );
}
var p = foo( 42 );
bar( p );
baz( p );
的 函 数 会立即执行(不会像 then(…) 中的回调一样异步延迟),它有两个参数,在
本例中我们将其分别称为 resolve 和 reject。这些是 promise 的决议函数。 resolve(…) 通常标识完成,而 reject(…) 则标识拒绝。
你可能会猜测 bar(…) 和 baz(…) 的内部实现或许如下:
function bar(fooPromise) { // 侦听foo(..)完成 fooPromise.then(
function(){
// foo(..)已经完毕,所以执行bar(..)的任务
},
function(){
// 啊,foo(..)中出错了! }
); }
// 对于baz(..)也是一样
Promise 决议并不一定要像前面将 Promise 作为未来值查看时一样会涉及发送消息。它也可
以只作为一种流程控制信号,就像前面这段代码中的用法一样。
另外一种实现方式是:
function bar() {
// foo(..)肯定已经完成,所以执行bar(..)的任务
}
function oopsBar() {
// 啊,foo(..)中出错了,所以bar(..)没有运行
}
// 对于baz()和oopsBaz()也是一样 var p = foo( 42 );
p.then( bar, oopsBar );
p.then( baz, oopsBaz );
如果以前有过基于 Promise 的编码经验的话,那你可能就会不禁认为前面代 码的最后两行可以用链接的方式写作 p.then( … ).then( … ), 而不是p.then(…); p.then(…)。但是,请注意,那样写的话意义就完全不 同了!目前二者的区别可能还不是很清晰,但与目前为止我们看到的相比, 这确实是一种不同的异步模式——分割与复制。别担心,对于这一点,本章 后面还会深入介绍。
这里没有把 promise p 传给 bar(…) 和 baz(…),而是使用 promise 控制 bar(…) 和 baz(…) 何时执行,如果执行的话。最主要的区别在于错误处理部分。
在第一段代码的方法里,不论 foo(…) 成功与否,bar(…) 都会被调用。并且如果收到了 foo(…) 失败的通知,它会亲自处理自己的回退逻辑。显然,baz(…) 也是如此。
在第二段代码中,bar(…) 只有在 foo(…) 成功时才会被调用,否则就会调用 oppsBar(…)。 baz(…) 也是如此。
这两种方法本身并谈不上对错,只是各自适用于不同的情况。 不管哪种情况,都是从 foo(…) 返回的 promise p 来控制接下来的步骤。
另外,两段代码都以使用 promise p 调用 then(…) 两次结束。这个事实说明了前面的观点, 就是 Promise(一旦决议)一直保持其决议结果(完成或拒绝)不变,可以按照需要多次 查看。
一旦 p 决议,不论是现在还是将来,下一个步骤总是相同的。