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