什麼是 Promise?Angular 裏的 $q 和 Promise 究竟是怎麼樣一種關係?在 Angular 裏使用 $q 究竟是怎麼樣一種體驗?本文將爲你一一解答。javascript
原文連接:All about $q and Promises in Angular
原文做者:Todd Motto
譯者:linpu.lijava
可能你曾經見過 $q
,或許已經在使用它了,可是可能你還沒發現它有一些很棒的特性,好比 $q.all()
和 $q.race()
。這篇文章將深刻 ES2015 的 Promise API 以及它是如何映射到 Angular 裏的 $q
。換句話說,這篇文章所有是關於 $q
的,但願你能喜歡!git
Promise 是一個特殊類型對象,咱們能夠直接使用或者構造新的實例來處理一個異步任務。把它稱做 Promise 是由於咱們被「承諾」了在將來的某個時間點會獲得一個結果。好比一個 HTTP 調用會在 200ms
或者 400ms
後完成,當完成以後就會有一個 Promise 執行。angularjs
一個 Promise 有三種狀態:pending、resolved 和 rejected。在 Angular 裏使用 $q
能夠構造一個新的 Promise,可是先讓咱們來看看 ES2015 裏的 Promise 對象來熟悉一下怎麼建立它。github
這裏主要的內容是 Promise
和做爲參數的 resolve
和 reject
。api
let promise = new Promise((resolve, reject) => {
if (/* 某個異步任務順利執行 */) {
resolve('Success!');
} else {
reject('Oops... something went wrong');
}
});
promise.then(data => {
console.log(data);
});複製代碼
咱們簡單地調用了 new Promise()
,在其內部能夠執行一個異步任務,這個任務可能封裝了一個特別的 DOM 事件,或者甚至是封裝了一些不是 Promise 對象的第三方庫。數組
舉個例子,封裝一個假定的第三方庫,叫作 myCallbackLib()
,它將提供一個 success 和 error 回調函數,咱們能夠在這個方法上構造一個 Promise,而後在相對應的位置去 resolve
和 reject
結果:promise
const handleThirdPartyCallback = someArgument => {
let promise = new Promise((resolve, reject) => {
// 假定某些不是 Promise 對象的第三方庫接口
// 但調用完成後會執行回調函數
myCallbackLib(someArgument, response => {
// we can resolve it with the response
resolve(response);
}, reason => {
// we can reject it with the reason
reject(reason);
});
});
return promise;
};
handleThirdPartyCallback({ user: 101 }).then(data => {
console.log(data);
});複製代碼
AngularJS 裏的 $q
實現如今已經和原生的 ES2015 Promise
對象同樣了,因此咱們能夠這麼寫:app
let promise = $q((resolve, reject) => {
if (/* 某個異步任務順利執行 */) {
resolve('Success!');
} else {
reject('Oops... something went wrong');
}
});
promise.then(data => {
console.log(data);
});複製代碼
和以前代碼惟一的區別就在於將 new Promise()
改爲了 $q
,變得足夠簡單了。異步
更理想的狀況是在一個 service 裏實現它:
function MyService($q) {
return {
getSomething() {
return $q((resolve, reject) => {
if (/* 某個異步任務順利執行 */) {
resolve('Success!');
} else {
reject('Oops... something went wrong');
}
});
}
};
}
angular
.module('app')
.service('MyService', MyService);複製代碼
以後就能夠將它注入一個 component 控制器:
const stuffComponent = {
template: ` <div> {{ $ctrl.stuff }} </div> `,
controller(MyService) {
this.stuff = [];
MyService.getSomething()
.then(data => this.stuff.unshift(data));
}
};
angular
.module('app')
.component('stuffComponent', stuffComponent);複製代碼
或者做爲一個 bindings
屬性在一個路由組件中使用,並映射到一個路由處理對象:
const stuffComponent = {
bindings: {
stuff: '<'
},
template: ` <div> {{ $ctrl.stuff }} </div> `,
controller(MyService) {
// your stuff already available
console.log(this.stuff);
}
};
const config = $stateProvider => {
$stateProvider
.state('stuff', {
url: '/stuff',
component: 'stuffComponent',
resolve: {
// resolve maps the `MyService` promise response
// Object across to `stuff` property, making it
// available as a binding inside the .component()
stuff: MyService => MyService.getSomething()
}
});
};
angular
.module('app')
.config(config)
.component('stuffComponent', stuffComponent);複製代碼
目前爲止咱們都只是看了一些假定的例子,下面的實現是我將一個 XMLHttpRequest
對象封裝成了一個基於 Promise 的解決方案,這種類型的實現應該是你建立本身的 $q
Promise 僅有的真實緣由吧:
let getStuff = $q((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(JSON.parse(xhr.responseText));
}
}
};
xhr.open('GET', '/api/stuff', true);
xhr.send();
});
getStuff.then(data => {
console.log('Boom!', data);
});複製代碼
請注意,這並非在倡導你建立和使用 XMLHttpRequest
,在 Angular 裏就使用 $http
,它已經爲你建立並返回了一個 Promise 對象:
function getStuff() {
return $http
.get('/api/stuff');
.then(data => {
console.log('Boom!', data);
});
}
getStuff().then(data => {
console.log('Boom!', data);
});複製代碼
這還意味着,你不能並且不該該像下面這樣作,由於下面至關於從一個已經存在的 Promise 對象裏建立一個 Promise 對象:
function getStuff() {
// 不要這麼作!
let defer = $q.defer();
$http
.get('/api/stuff');
.then(response => {
// 不要這麼作!
$q.resolve(response);
}, reason => {
// 不要這麼作!
$q.reject(reason);
});
return defer.promise;
}
getStuff().then(data => {
console.log('Boom!', data);
});複製代碼
黃金法則:只對自己沒有 Promise 的東西使用 $q
!雖然只在這種狀況下建立 Promise,可是你能夠對其餘的 Promise 使用一些其餘的方法,好比 $q.all()
和 $q.race()
。
使用 $q.defer()
只是 $q
做爲構造函數的另一種風格和原始實現。假設下面的代碼,改自以前使用 service 的例子:
function MyService($q) {
return {
getSomething() {
let defer = $q.defer();
if (/* some async task is all good */) {
defer.resolve('Success!');
} else {
defer.reject('Oops... something went wrong');
}
return defer.promise;
}
};
}複製代碼
當你想要當即從一個非 Promise 對象中處理一個 Promise 的時候就可使用 $q.when()
或者 $q.resolve()
(它們是同樣的,$q.resolve()
是 $q.when()
的一個別名,爲了符合 ES2015 Promise 的命名約定),舉個例子:
$q.when(123).then(res => {
// 123
console.log(res);
});
$q.resolve(123).then(res => {
// 123
console.log(res);
});複製代碼
注意:$q.when()
也是和 $q.resolve()
同樣的。
使用 $q.reject()
會當即拒絕掉一個 Promise,這麼作是爲了方便一些狀況作處理,好比在 HTTP 攔截器沒有任何返回的時候,就能夠返回一個拒絕掉的 Promise 對象:
$httpProvider.interceptors.push($q => ({
request(config) {...},
requestError(config) {
return $q.reject(config);
},
response(response) {...},
responseError(response) {
return $q.reject(response);
}
}));複製代碼
有的時候你可能須要一次性處理多個 Promise,經過 $q.all()
就能夠輕易實現,只需傳遞進 Promise 的數組或者對象,接着就會在全部 Promise 都處理完後調用 .then()
方法:
let promiseOne = $http.get('/api/todos');
let promiseTwo = $http.get('/api/comments');
// Promise 數組
$q.all([promiseOne, promiseTwo]).then(data => {
console.log('Both promises have resolved', data);
});
// Promise 對象哈希
// 這是 ES2015 對 { promiseOne: promiseOne, promiseTwo: promiseTwo } 的簡寫
$q.all({
promiseOne,
promiseTwo
}).then(data => {
console.log('Both promises have resolved', data);
});複製代碼
$q.race()
是 Angular 裏面的一個新方法,和 $q.all()
相似,可是它只會返回第一個處理完成的 Promise 給你。假定 API 調用 1 和 API 調用 2 同時執行,而 API 調用 2 在 API 調用 1 以前處理完成,那麼你就只會獲得 API 調用 2 的返回對象。換句話說,最快(處理完成)的 Promise 會贏得返回對象的機會:
let promiseOne = $http.get('/api/todos');
let promiseTwo = $http.get('/api/comments');
// Promise 數組
$q.race([promiseOne, promiseTwo]).then(data => {
console.log('Fastest wins, who will it be?...', data);
});
// Promise 對象哈希
// 這是 ES2015 對 { promiseOne: promiseOne, promiseTwo: promiseTwo } 的簡寫
$q.race({
promiseOne,
promiseTwo
}).then(data => {
console.log('Fastest wins, who will it be?...', data);
});複製代碼
使用 $q
對非 Promise 的對象或回調構造 Promise,利用 $q.all()
和 $q.race()
處理已存在的 Promise。
還想看更多內容,$q 文檔送上。
本文同步於個人我的博客,歡迎你們討論指正。