Promise 是一种异步处理模式。
Promise 这种规范,它能帮助开发者用同步的方式,编写异步的代码。
再说的直白点,Promise 就是一种对执行结果不确定的一种预先定义,如果成功,就 xxxx;如果失败,就 xxxx,就像事先给出了一些承诺。
使用 promise 机制的优点如下: 1.可以对函数进行链式调用,所以你不会陷入代码缩进噩梦中; 2.在调用链的过程中,可以保证上一个函数调用完成之后才会调用下一个函数; 3.每一个 then()调用都带有两个参数(两个都是函数)。第一个是成功之后的回调,第二个是出错之后的处理器; 4.如果调用链中出现了错误,错误将会被冒泡传递到其余的错误处理函数中。所以,最终来说,所有错误都可以在任意一个回调函数中进行处理。
Promise 背后的概念非常简单,有两部分:
Deferreds,定义工作单元,
Promises,从 Deferreds 返回的数据。
基本上,你会用 Deferred 作为通信对象,用来定义工作单元的开始,处理和结束三部分。
Promise 是 Deferred 响应数据的输出;它有状态 (等待,执行和拒绝),以及句柄,或叫做回调函数,反正就是那些在 Promise 执行,拒绝或者提示进程中会被调用的方法。
Promise 不同于回调的很重要的一个点是,你可以在 Promise 状态变成执行(resolved)之后追加处理句柄。这就允许你传输数据,而忽略它是否已经被应用获取,然后缓存它,等等之类的操作,因此你可以对数据执行操作,而不管它是否已经或者即将可用。
$q服务
$q 服务是 AngularJS 中自己封装实现的一种 Promise 实现,相对与 Kris Kwal’s Q 要轻量级的多。
先介绍一下$q 常用的几个方法:
1)defer() 创建一个 deferred 对象,这个对象可以执行几个常用的方法,比如 resolve,reject,notify 等.这个对象有 promise 属性。
2)all()
3)when() 传入一个不确定的参数,如果符合 Promise 标准,就返回一个 promise 对象。
defer()方法:
defer()用于创建一个 deferred 对象,defer.promise 用于返回一个 promise 对象,来定义 then 方法。then 中有三个参数,分别是成功回调、失败回调、状态变更回调。
在$q 中,可以使用 resolve 方法,变成完成状态;使用 reject 方法,变成拒绝状态。
all()方法:
$q.all(),允许你等待并行的 promise 处理,当所有的 promise 都被处理结束之后,调用共同的回调。在 Angular 中,这个方法有两种调用方式:以 Array 方式或 Object 方式。Array 方式接收多个 promise,然后在调用.then()的时候使用一个数据结果对象,在结果对象里面包含了所有的 promise 结果,按照输入数组的顺序排列。
var funcA = function () {
console.log('funcA');
return 'hello,funA';
};
var funcB = function () {
console.log('funcB');
return 'hello,funB';
};
$q.all([funcA(), funcB()]).then(function (results) {
console.log(results[0]);
console.log(results[1]);
});
第二种方式是接收一个 promise 集合对象,允许你给每个 promise 一个别名,在回调函数中可以使用它们(有更好的可读性)。
$q.all({ first: funcA(), second: funcB() }).then(function (results) {
console.log(results.first);
console.log(results.second);
});
建议使用数组表示法,如果你只是希望可以批处理结果,就是说,如果你把所有的结果都平等处理。而以对象方式来处理,则更适合需要自注释代码的时候。
when()方法:
如果你想通过一个普通变量创建一个 promise,或者你不清楚你要处理的对象是不是 promise 时非常有用。
$q.when()在诸如服务中的缓存这种情况也很好用:
angular.module('myApp').service('MyService', function ($q, MyResource) {
var cachedSomething;
this.getSomething = function () {
if (cachedSomething) {
return $q.when(cachedSomething);
}
// on first call, return the result of MyResource.get()
// note that 'then()' is chainable / returns a promise,
// so we can return that instead of a separate promise object
return MyResource.get().$promise.then(function (something) {
cachedSomething = something;
});
};
});
与这个类似?
angular.module('MyService', []).factory('githubService', [
'$q',
'$http',
function ($q, $http) {
var getPullRequests = function () {
var deferred = $q.defer();
var promise = deferred.promise;
var progress;
$http
.get('https://api.github.com/repos/angular/angular.js/pulls')
.success(function (data) {
var result = [];
for (var i = 0; i < data.length; i++) {
result.push(data[i].user);
progress = ((i + 1) / data.length) * 100;
deferred.notify(progress);
}
deferred.resolve(result);
})
.error(function (error) {
deferred.reject(error);
});
return promise;
};
return {
getPullRequests: getPullRequests,
};
},
]);
然后可以这样调用它:
MyService.getSomething().then(function (something) {
console.log(something);
});
在 Promise 中,定义了三种状态:等待状态,完成状态,拒绝状态。
关于状态有几个规定: 1)状态的变更是不可逆的 2)等待状态可以变成完成或者拒绝
promise 对象有另外三个方法:.then(),是唯一 Promise 规范要求的方法,用三个回调方法作为参数;一个成功回调,一个失败回调,还有一个状态变化回调。
$q 在 Promise 规范之上还添加了两个方法: catch(),可以用于定义一个通用方法,它会在 promise 链中有某个 promise 处理失败时被调用。还有 finally(),不管 promise 执行是成功或者失败都会执行。注意,这些不应该和 Javascript 的异常处理混淆或者并用: 在 promise 内部抛出的异常,不会 catch()俘获
链式 Promise
Promise 链会把上一个 then 的返回结果传递给调用链的下一个 then(如果没有就是 undefined)
如果 then 回调返回一个 promise 对象,下一个 then 只会在这个 promise 被处理结束的时候调用。
在链最后的 catch 为整个链式处理提供一个异常处理点
在链最后的 finally 总是会被执行,不管 promise 被处理或者被拒绝,起清理作用
拦截响应
Promise 机制还可以做一些非常酷的事情:拦截响应。
我们已经学过的内容有:向服务端发送请求、处理响应、把响应很好地包装成抽象的东西及处理异步调用。但是在真实的应用中,对于每一次服务端调用,最终还必须做一些通用的操作,例如错误处理、鉴权以及其他安全方面的处理(例如剪裁数据)。
在深入理解了$q 接口之后,我们就可以使用拦截响应的方式来处理以上所有任务了。响应拦截的机制允许我们在响应到达应用之前对其进行拦截,并在上面进行一些操作,例如转换数据形式、处理错误等所有你能想到的操作。
下面来看一个例子,它会拦截响应,然后做一些很小的数据转换操作。
//把拦截器注册为一个服务
myModule.factory('myInterceptor', function ($q, notifyService, errorLog) {
return function (promise) {
return promise.then(
function (response) {
//什么都不做
return response;
},
function (response) {
//notify服务将会使用错误信息来刷新UI
notifyService(response);
//同时把错误信息打印到控制台,以便调试
errorLog(response);
return $q.reject(response);
}
);
};
});
//确保我们所创建的拦截器是拦截器链的一部分
$httpProvider.responseInterceptors.push('myInterceptor');
参考:
AngularJS 中的 Promise — $q 服务详解
Promise 的前世今生和妙用技巧
AngularJS 中的 Promise 和设计模式
剖析 Promise 内部结构