Angular 学习之Promise

因为 Angular API 文档是全英文的,自己英语又这么渣,看了两行都看不下去了,只能自行翻译了(其实是自己汉字重新表达的)

原文传送门:Angular 学习之$q

简介

$q 是 AngularJS 内置的一种服务,可以让你在运行异步函数时使用函数运行完成后的返回值(或者是个异常)。
这是受Kris Kowal’s启发后,在 AngularJS 中实现的Promises/A+
$q 可以以两种方式使用:

  • 类似于 Kris Kowal’s Q 或者 jQuery’s Deferred
  • 在某种程度上类似于 ES6 (ES2015) promises

$q constructor

ES6 流式风格的 promise 本质上是使用 $q 作为构造函数,并将 resolver 函数作为第一个参数传入构造函数。这和 ES6 中底层 Promise 的实现很相似,可以查看MDN
尽管这种构造函数的方式在 AngularJS 是支持的,但这并不代表 ES6 Promise 的所有方法在 AngularJS 中都支持。
这种方式可以这么使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// for the purpose of this example let's assume that variables `$q` and `okToGreet`
// are available in the current lexical(词汇的,字典的) scope (they could have been injected or passed in).
function asyncGreet(name) {
// perform some asynchronous operation, resolve or reject the promise when appropriate.
return $q(function(resolve, reject) {
setTimeout(function() {
if (okToGreet(name)) {
resolve('Hello, ' + name + '!');
} else {
reject('Greeting ' + name + ' is not allowed.');
}
}, 1000);
});
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
});

注意:ES6 风格的 progress/notify 回调接口目前 AngularJS 并不支持。
注意:不想 ES6 的实现,构造函数中抛出了异常并不会自动触发 reject 回调。

然而,传统的 CommonJS-style 用法在 AngularJS 中仍然是可用的,下面是它的文档。
The CommonJS Promise proposal将 promise 作为和一个对象交互的接口,它会返回一个异步动作的结果,在某一时刻该异步动作的结果有可能返回也有可能不返回。
从错误处理的角度来讲,deferred 和 promise APIs 是异步编程,而 try, catchthrow 是同步编程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// for the purpose of this example let's assume that variables `$q` and `okToGreet`
// are available in the current lexical scope (they could have been injected or passed in).
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
}, 1000);
return deferred.promise;
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
}, function(update) {
alert('Got notification: ' + update);
});

首先,这种稍显复杂一点的编码方式的价值并不是很明显。promise 和 deferred APIs 带来的好处,可以参考:https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md
另外,promise 可以让你整合传统的回调方法。如果想了解更多这方面信息,请参考Q documentation,特别是并行和串行添加 promises 这个章节。

The Deferred API

通过调用 $q.defer() 可以构造一个 deferred 实例。
deferred 对象的作用就是以 APIs 的形式暴露出关联的 Promise 实例,而 Promise 实例是可以用于检测异步动作是否执行成功,也可以查看异步任务的状态。

Methods

  • resolve(value)-使用参数 value 解析出 promise。如果 value 是通过 $q.reject 构造的 rejection,promise 将会被 rejected 代替。
  • rejected(reason)-使用参数 reason 拒绝 promise。这等价于使用 $q.reject 构造一个 rejection 来作为 resolve 的参数。
  • notify(value)-在 promise 执行过程中提供更新状态码的功能。在 promise 解析或拒绝的过程中可以被调用多次。

Properties

  • promise-{Promise}-这个 deferred 关联的 promise 对象。

The Promise API

创建了一个 deferred 实例后,通过调用 deferred.promise 就可以得到一个 promise 实例。
promise 对象的作用就是可以让对异步任务的结果感兴趣者能够访问异步 deferred 任务完成后的结果。

Methods

  • then(successCallback, [errorCallback], [notifyCallback])-无论 promise 什么时候执行了 resolved 或者 rejected,then 都会使用成功回调或者错误回调尽可能快的调用异步任务的结果。成功回调或者错误回调都只有一个参数:就是异步任务执行的结果或者 rejection 的原因。另外,在 promise resolved 或者 rejected 之前,notify 回调函数会被调用零次或者多次,用来提供其处理进度。这个方法会返回一个新的 promise,这个新的 promise 会通过返回successCallback,errorCallback来 resolved 或者 rejected(除非 value 是一个 promise,如果是那样的话,最终会通过 [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)执行)。通过notifyCallback` 方法的返回值,它也会执行通知。promise 不能在 notifyCallback 方法中被 resolved 或者 rejected。当然了,errorCallback 和 notifyCallback 这两个参数是可选的。
  • catch(errorCallback)-相当于promise.then(null, errorCallback)` 的缩写
  • `finally(callback, notifyCallback)-允许你在不改变最终值的前提下检测到 promise 的执行或者 rejection。这对于不论 promise 被 rejected 还是 resolved,最终都需要释放资源或者做一些清理工作很有用。如果需要更多这方面的信息,可以参考full specification

Chaining promises

因为调用 promise 的 then 方法会返回一个新的 promise,所以可以很容易的创建 promise 的链式调用:

1
2
3
4
5
6
promiseB = promiseA.then(function(result) {
return result + 1;
});
// promiseB will be resolved immediately after promiseA is resolved and its value
// will be the result of promiseA incremented by 1

我们可以创建任意长度的 promise 链式调用,因为一个 promise 可以使用其他的 promise resolved(这将会进一步推迟其解析过程),因此我们可以在链式调用的任意一点暂停或推迟 promise 的解析。$http’s 拦截器就是通过这种形式实现的。

Differences between Kris Kowal’s Q and $q

它们主要有两点不同:

  • $q 被集成在 $rootScope.Scope 中,Scope 数据模型在 angular 中实现了观察者机制,这意味着可以在避免浏览器重绘 UI 的情况下更快的解析或者拒绝 promise。
  • 相对 $q,Q有更多的特性,但是那也花费了一定的代价。$q 是包含了异步任务所需要的重要功能的最小实现。

Testing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));

Dependencies

$rootScope

Usage

$q(resolver);

Arguments

Param Type Details
resolver function(function, function) 对于新创建的 promise 来说,第一个参数 function 解析这个 promise,第二个参数 function 拒绝这个 promise

Returns

Promise 新创建的 promise

Methods

  • defer(): 创建一个代表着异步任务的 Deferred 对象,返回值就是这个 Deferred 实例
  • reject(reason):创建一个使用指定 reason 拒绝的 promise。这个方法应该用在 promise 链接调用时拒绝一个 promise。如果你正在处理链式调用的最后一个 promise,那么你不必关心它。如果你把 deferreds/promises 和 catch/throw 做比较的话,你可以把 reject 当做 throw 关键字。这也相当于通过 promise 错误回调,你捕获了一个错误,并且你想要向前传递这个错误,所以你不得不 rethrow 这个错误通过 reject。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    promiseB = promiseA.then(function(result) {
    // success: do something and resolve promiseB
    // with the old or a new result
    return result;
    }, function(reason) {
    // error: handle the error if possible and
    // resolve promiseB with newPromiseOrValue,
    // otherwise forward the rejection to promiseB
    if (canHandle(reason)) {
    // handle the error and recover
    return newPromiseOrValue;
    }
    return $q.reject(reason);
    });

reason 参数可以为任意类型:常量,消息,异常或者一个代表着拒绝原因的对象。返回值是一个使用 reason 拒绝的 promise。

  • when(value, [successCallback], [errorCallback], [progressCallback]):将一个值或者第三方的 then-able promise 包装成 $q promise。当你处理一个有可能是 promise 也有可能不是 promise 的对象时,或者处理一个来自不受信任源的 promise 时,该方法比较有用。它的参数含义如下:
Param Type Details
value * Value or promise
successCallback(optional) Function=
errorCallback(optional) Function=
progressCallback(optional) Function=

该方法返回值是一个传入参数转化后的 promise。

  • resolve(value, [successCallback], [errorCallback], [progressCallback]):该方法是 ES6 语法中的 when 方法的别名,这里主要是为了维持命名一致性。它的参数含义如下:
Param Type Details
value * Value or promise
successCallback(optional) Function=
errorCallback(optional) Function=
progressCallback(optional) Function=

该方法返回值也是一个 promise。

  • all(promises):将多个 promises 结合为一个 promise,当所有的 promise 被解析后也就是这个结合体被解析。它的参数就是一个 promise 数组或以 promise 为值的 hash 映射对象。该方法返回值就是结合体 promise,所有 promise 都被解析时结合体 promise 才会被解析,只要有一个 promise 被拒绝,结合体 promise 就会被拒绝。
  • race(promises):基本含义跟 all 方法相同,但是会结合体 promise 会尽可能快的被解析或者拒绝。

拓展学习

  1. 看完了这个文档,啥也没学会,还是看中文的好:Promise 对象
  2. 还是熟悉的汉字:jQuery的deferred对象详解

单词学习

  • compliant,服从的,顺从的
  • resemble,相似,类似于
  • essentially ,本质上
  • implicitly,含蓄地,暗中的
  • expose,暴露,公开
  • derived,推断出,衍生出
  • fulfillment,执行,实行
  • defer,推迟,服从
  • flickering,闪烁
  • consistency,一致性