本文將為你介紹路由的高級特性,這些高級特性可以用于處理項目復(fù)雜的異步邏輯。
關(guān)于單詞promises,直譯是承諾,但是個人覺得還是使用原文吧。讀起來順暢點。
Ember的路由處理異步邏輯的方式是使用Promise。簡而言之,Promise就是一個表示最終結(jié)果的對象。這個對象可能是fulfill
(成功獲取最終結(jié)果)也可能是reject
(獲取結(jié)果失?。榱双@取這個最終值,或者是處理Promise失敗的情況都可以使用then
方法,這個方法接受兩個可選的回調(diào)方法,一個是Promise獲取結(jié)果成功時執(zhí)行,一個是Promise獲取結(jié)果失敗時執(zhí)行。如果promises獲取結(jié)果成功那么獲取到的結(jié)果將作為成功時執(zhí)行的回調(diào)方法的參數(shù)。相反的,如果Promise獲取結(jié)果失敗,那么最終結(jié)果(失敗的原因)將作為Promise失敗時執(zhí)行的回調(diào)方法的參數(shù)。比如下面的代碼段,當(dāng)Promise獲取結(jié)果成功時執(zhí)行fulfill
回調(diào),否則執(zhí)行reject
回調(diào)方法。
// app/routes/promises.js
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel: function() {
console.log('execute model()');
var promise = this.fetchTheAnswer();
promise.then(this.fulfill, this.reject);
},
// promises獲取結(jié)果成功時執(zhí)行
fulfill: function(answer) {
console.log("The answer is " + answer);
},
// promises獲取結(jié)果失敗時執(zhí)行
reject: function(reason) {
console.log("Couldn't get the answer! Reason: " + reason);
},
fetchTheAnswer: function() {
return new Promise(function(fulfill, reject){
return fulfill('success'); //如果返回的是fulfill則表示promises執(zhí)行成功
//return reject('failure'); //如果返回的是reject則表示promises執(zhí)行失敗
});
}
});
上述這段代碼就是promises的一個簡單例子,promises的then
方法會根據(jù)promises的獲取到的最終結(jié)果執(zhí)行不同的回調(diào),如果promises獲取結(jié)果成功則執(zhí)行fulfill
回調(diào),否則執(zhí)行reject
回調(diào)。
promises的強(qiáng)大之處不僅僅如此,promises還可以以鏈的形式執(zhí)行多個then
方法,每個then方法都會根據(jù)promises的結(jié)果執(zhí)行fulfill
或者reject
回調(diào)。
// app/routes/promises.js
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel() {
// 注意Jquery的Ajax方法返回的也是promises
var promiese = Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls');
promiese.then(this.fetchPhotoOfUsers)
.then(this.applyInstagramFilters)
.then(this.uploadThrendyPhotAlbum)
.then(this.displaySuccessMessage, this.handleErrors);
},
fetchPhotoOfUsers: function(){
console.log('fetchPhotoOfUsers');
},
applyInstagramFilters: function() {
console.log('applyInstagramFilters');
},
uploadThrendyPhotAlbum: function() {
console.log('uploadThrendyPhotAlbum');
},
displaySuccessMessage: function() {
console.log('displaySuccessMessage');
},
handleErrors: function() {
console.log('handleErrors');
}
});
這種情況下會打印什么結(jié)果呢??
在前的文章已經(jīng)使用過Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls');
獲取數(shù)據(jù),是可以成功獲取數(shù)據(jù)的。所以promises獲取結(jié)果成功,應(yīng)該執(zhí)行的是獲取成功對應(yīng)的回調(diào)方法。瀏覽器控制臺打印結(jié)果如下:
fetchPhotoOfUsers
applyInstagramFilters
uploadThrendyPhotAlbum
displaySuccessMessage
但是如果我把Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls');
改成一個不存在的URL,比如改成Ember.$.getJSON('https://www.my-example.com');
執(zhí)行代碼之后控制臺會提示出404
錯誤,并且打印'handleErrors'。說明promises獲取結(jié)果失敗,執(zhí)行了then里的reject回調(diào)。為了驗證每個回調(diào)的reject
方法再修改修改代碼,如下:
// app/routes/promises.js
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel() {
// 注意Jquery的Ajax方法返回的也是promises
var promiese = Ember.$.getJSON(' https://www.my-example.com ');
promiese.then(this.fetchPhotoOfUsers, this.fetchPhotoOfUsersError)
.then(this.applyInstagramFilters, this.applyInstagramFiltersError)
.then(this.uploadThrendyPhotAlbum, this.uploadThrendyPhotAlbumError)
.then(this.displaySuccessMessage, this.handleErrors);
},
fetchPhotoOfUsers: function(){
console.log('fetchPhotoOfUsers');
},
fetchPhotoOfUsersError: function() {
console.log('fetchPhotoOfUsersError');
},
applyInstagramFilters: function() {
console.log('applyInstagramFilters');
},
applyInstagramFiltersError: function() {
console.log('applyInstagramFiltersError');
},
uploadThrendyPhotAlbum: function() {
console.log('uploadThrendyPhotAlbum');
},
uploadThrendyPhotAlbumError: function() {
console.log('uploadThrendyPhotAlbumError');
},
displaySuccessMessage: function() {
console.log('displaySuccessMessage');
},
handleErrors: function() {
console.log('handleErrors');
}
});
由于promises獲取結(jié)果失敗故執(zhí)行其對應(yīng)的失敗處理回調(diào)。這種調(diào)用方式有點類似于try……catch……
,但是本文的重點不是講解promises,更多有關(guān)promises的教材請讀者自行Google或者百度吧,在這里介紹一個js庫RSVP.js,它可以讓你更加簡單的組織你的promises代碼。
在附上幾個promises的參考網(wǎng)站:
極力推薦看第二個網(wǎng)站的教材,這個網(wǎng)站可以直接運行js代碼。還有源碼和PDF。非常棒!??!
當(dāng)發(fā)生路由切換的時候,在model
回調(diào)(或者是beforeMode
、afterModel
)中獲取的數(shù)據(jù)集會在切換完成的時候傳遞到路由對應(yīng)的controller
上。如果model回調(diào)返回的是一個普通的對象(非promises對象)或者是數(shù)組,路由的切換會立即執(zhí)行,但是如果model回調(diào)返回的是一個promises對象,路由的切換將會被中止直到promises執(zhí)行完成(返回fulfill
或者是reject
)才切換。
路由器任務(wù)任何一個包含了then方法的對象都是一個promises。
如果promises獲取結(jié)果成功則會從被中止的地方繼續(xù)往下執(zhí)行或者是執(zhí)行路由鏈的下一個路由,如果promises返回的依然是一個promises,那么路由依然再次被中止,等待promises的返回結(jié)果,如果是fulfill
則從被中止的地方開始往下執(zhí)行,以此類推,一直到獲取到model
回調(diào)所需的結(jié)果。
傳遞到每個路由的setupController
回調(diào)的值都是promises返回fulfill
時的值。如下代碼:
// app/routes/tardy.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return new Ember.RSVP.Promise(function(resolver) {
console.log('start......');
Ember.run.later(function() {
resolver({ msg: 'Hold your horses!!'});
}, 3000);
});
},
setupController(controller, model) {
console.log('msg = ' + model.msg);
}
});
一進(jìn)入路由tardy
,model
回調(diào)就會被執(zhí)行并且返回一個延遲3秒才執(zhí)行的promises,在這期間路由會中止。當(dāng)promises返回fulfill
路由會繼續(xù)執(zhí)行,并將model
返回的對象傳遞到setupController
方法中。
雖然這種中止的行為會影響響應(yīng)速度但是這是非常必要的,特別是你需要保證model
回調(diào)得到的數(shù)據(jù)是完整的數(shù)據(jù)的時候。
文章前面主要講的是promises獲取結(jié)果成功的情況,但是如果是獲取結(jié)果失敗的情況又是怎么處理呢??
默認(rèn)情況下,如果model
回調(diào)返回的是一個promises對象并且此promises返回的是reject
,此時路由切換將被終止,也不會渲染對應(yīng)的模板,并且會在瀏覽器控制臺打印出錯誤日志信息,例子promises-ret-reject.js
會演示。
你可以自定義處理出錯信息的邏輯,只要在route
的actions
哈希對象中配置即可。當(dāng)promises獲取結(jié)果失敗的默認(rèn)情況下會執(zhí)行一個名為error
的處理事件,否則會執(zhí)行你自定義的處理事件。
// app/routes/promises-ret-reject.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
// 為了測試效果直接返回reject
return Ember.RSVP.reject('FAIL');
},
actions: {
error: function(reason) {
console.log('reason = ' + reason);
// 如果你想讓這個事件冒泡到頂級路由application只需要返回true
// return true;
}
}
});
如果沒有不允許事件冒泡打印結(jié)果僅僅是reason = FAIL
。并且頁面上什么都不顯示(不渲染模板)。
如果去掉最后一行代碼的注釋,讓事件冒泡到頂級路由application
中的默認(rèn)方法處理,那么結(jié)果又是什么呢?
結(jié)果是先打印了處理結(jié)果,然后再打印出提示錯誤的日志信息。并且頁面上什么都不顯示(不渲染模板)。
在前面第3點介紹了promises獲取結(jié)果失敗時會終止路由轉(zhuǎn)換,但是如果model
返回是一個promises鏈呢?程序能到就這樣死了?。?!顯然是不行的,做法是把model回調(diào)中返回的reject
轉(zhuǎn)換為fulfill
。這樣就可以繼續(xù)執(zhí)行或者切換到下一個路由了!
// app/routes/funky.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var promises = Ember.RSVP.reject('FAIL');
// 由于已經(jīng)知道promises返回的是reject,所以fulfill回調(diào)直接寫為null
return promises.then(null, function() {
return { msg: '恢復(fù)reject狀態(tài):其實就是在reject回調(diào)中繼續(xù)執(zhí)行fulfill狀態(tài)下的代碼。' };
});
}
});
為了驗證model
回調(diào)的結(jié)果,直接在模板上顯示msg
。
funky模板
<br>
{{model.msg}}
執(zhí)行URL:http://localhost:4200/funky,得到如下結(jié)果:
說明model回調(diào)進(jìn)入到reject回調(diào)中,并正確返回了預(yù)期結(jié)果。
到本文為止有關(guān)路由這以整章的內(nèi)容也全部介紹完畢了??!難點在Ember.js 入門指南之二十六查詢參數(shù)這一篇。能力有限沒有把這篇的內(nèi)容講明白,暫時擱下待日后完善!
總的來說路由主要職責(zé)是獲取數(shù)據(jù),根據(jù)邏輯處理數(shù)據(jù)。有點MVC架構(gòu)的dao層,專門做數(shù)據(jù)的CRUD操作。當(dāng)然另外一個重要職責(zé)就是路由的切換,以及切換的時候參數(shù)的設(shè)置問題。
結(jié)束完這一章下一章接著介紹組件(Component)。
博文完整代碼放在Github(博文經(jīng)過多次修改,博文上的代碼與github代碼可能又出入,不過影響不大!),如果你覺得博文對你有點用,請在github項目上給我點個star
吧。您的肯定對我來說是最大的動力!!
更多建議: