本节介绍了路由器的一些更高级的功能和处理复杂异步逻辑的能力。
一、A word on promises
1. 在Ember的Router中Ember使用了大量的Promises概念来处理异步逻辑。简而言之,promises就是一个代表最终值的对象。一个promise可以fulfill(成功解析值)或者reject(解析值失败)。
2. 检索这个最终值的方法或者当promise reject的时候处理这些情况是通过promise的then方法。then方法接受两个可选的回调,一个是为了fulfillment,另外一个是为了rejection。
3.如果promise执行,fulfillment handler被调用并且用fulfilled value作为唯一的参数,如果promise被拒绝了,rejection handler会被调用并把拒绝的原因reason for the rejection作为它的唯一参数:
example:
var promise = fetchTheAnswer(); promise.then(fulfill, reject); function fulfill(answer) { console.log("The answer is " + answer); } function reject(reason) { console.log("Couldn't get the answer! Reason: " + reason); }
4. promises大部分的力量来自于一个事实,它们可以被链接在一起执行顺序的异步操作:
// Note: jQuery AJAX methods return promises var usernamesPromise = Ember.$.getJSON('/usernames.json'); usernamesPromise.then(fetchPhotosOfUsers) .then(applyInstagramFilters) .then(uploadTrendyPhotoAlbum) .then(displaySuccessMessage, handleErrors);
fetchPhotosOfUsers
, applyInstagramFilters
, 或者uploadTrendyPhotoAlbum方法中的任何一个返回一个reject promise,handleErrors将会携带着失败的原因被调用。
5. guid中不打算完全深入到可以使用所有不同方式的promises,但是如果你想更深入的学习,请查看RSVP,它是Ember使用的promise库。
二、The router pauses for promises
1. 当在路由器之间跳转的时候,Ember路由器收集所有的在跳转结束时被传递给路由controllers的models(通过model hook)。
如果model hook(或者相关的beforeModel, afterModel hooks)返回正常的(non-promise)的对象或者数组,跳转将会立即完成。
但是如果model hook(或者相关的beforeModel, afterModel hooks)返回了一个promise(或者如果一个promise被作为一个提供给transitionTo的参数),跳转将会暂停,知道这个promise执行(fullfill)或者被拒绝(reject)。
2. 路由器考虑把任何有then方法的对象定义成一个promise。
3. 如果这个promise执行了(fullfill),跳转将到它离开的地方并开始解析下一个路由的model。
4. 一个基本的例子:
app/routes/tardy.js
export default Ember.Route.extend({ model() { return new Ember.RSVP.Promise(function(resolve) { Ember.run.later(function() { resolve({ msg: "Hold Your Horses" }); }, 3000); }); }, setupController(controller, model) { console.log(model.msg); // "Hold Your Horses" } });
5. 当你需要保证在展示一个新模板之前路由的数据完全被加载,这种pause-on-promise是非常有价值的。
三、When promises reject...
我们已经涵盖了一个model promise履行的情况下,但如果它拒绝?
1. 默认的,如果一个model promise在跳转之间被拒绝,跳转就被终止,没有新的目标路由模板被渲染,并将错误记录到控制台。
2. 你可以通过在路由的actions hash中的error handler来配置错误处理程序。当一个promise被拒绝,一个error事件将会在路由上被激活并且冒泡到route:application的默认错误处理程序上,除非它通过一个自定义错误处理器的方式被处理。
3. example:
app/routes/good-for-nothing.js
export default Ember.Route.extend({ model() { return Ember.RSVP.reject("FAIL"); }, actions: { error(reason) { alert(reason); // "FAIL" // Can transition to another route here, e.g. // this.transitionTo('index'); // Uncomment the line below to bubble this error event: // return true; } } });
四、Recovering from rejection
拒绝的model promise终止跳转,但是因为promises是连贯性的,你可以在model hook中捕捉到拒绝的promise它自己,并且把它们转换为实现,这不会终止跳转。
app/routes/funky.js
export default Ember.Route.extend({ model() { return iHopeThisWorks().then(null, function() { // Promise rejected, fulfill with some default value to // use as the route's model and continue on with the transition return { msg: "Recovered from rejected promise" }; }); } });
五、Beforemodel and Aftermodel
1. 对于pause-on-promise跳转,model hook包含了多个用例,但有时你将需要相关的beforeModel和afterModel hooks的帮助。最普遍的原因就是,如果通过{{link-to}}或者transitionTo(而不是由一个网址变化引起的变化)你跳转到一个含有动态URL字段的路由,这个你正在跳转的路由的model已经被指定了(例如{{#link-to 'article' article}}
or this.transitionTo('article', article)
),在这种情况下model hook不会被调用。
在这种情况下,你需要使用beforModel或者afterModel hook来存储逻辑,同时路由器任然在收集所有路由的models来执行跳转。
2. beforModel
很容易对两者比较有用的,beforeModel hook在路由器尝试为给定的路由解析model之前会被调用。换句话说,它在model hook被调用之前被调用,或者如果model没有被调用,它在路由器尝试解析任何传递给这个路由的model promises之前被调用。
比如model,从beforeModel返回一个promise将会暂停跳转在直到它被解析,或者如果被拒绝将会激活一个eroor。
3. 下面是一个用例的far-from-exhaustive列表,这里beforeModel是非常方便的:
export default Ember.Route.extend({ beforeModel() { if (!this.controllerFor('auth').get('isLoggedIn')) { this.transitionTo('login'); } } });
4. afterModel
1. afterModel hook在一个route's model(可能是一个promise)被解析之后被调用,并且遵循和beforeModel同样的pause-on-promise语法作为model。它被传递到已经被解析的model并且因此,执行任何依赖于完全解析的model的值的额外的逻辑。
2. example
app/routes/articles.js
export default Ember.Route.extend({ model() { // `this.store.findAll('article')` returns a promise-like object // (it has a `then` method that can be used like a promise) return this.store.findAll('article'); }, afterModel(articles) { if (articles.get('length') === 1) { this.transitionTo('article.show', articles.get('firstObject')); } } });
3. 你也许会问,为什么我们不能只是把afterModel逻辑放入从model返回的promise的fulfill handler: 原因是,正如上面所提到的,跳转通过{{link-to}}或者transitionTo初始化,很可能已经为路由提供了model,所以model在这些情况下不能被调用。