In addition to the techniques described in the Asynchronous Routing Guide, the Ember Router provides powerful yet overridable conventions for customizing asynchronous transitions between routes by making use of error and loading substates.

除了异步路由指南中描述的技术外,Ember路由还提供了通过使用errorloading子状态,来实现自定义路由间的异步过渡。

loading substates

loading子状态

Consider the following:

考虑下面的情形:

  1. App.Router.map(function() {
  2. this.resource('articles', function() { // -> ArticlesRoute
  3. this.route('overview'); // -> ArticlesOverviewRoute
  4. });
  5. });

If you navigate to articles/overview, and in ArticlesRoute#model, you return an AJAX query promise to load all of the articles that takes a long time to complete. During this time, your UI isn’t really giving you any feedback as to what’s happening; if you’re entering this route after a full page refresh, your UI will be entirely blank, as you have not actually finished fully entering any route and haven’t yet displayed any templates; if you’re navigating to articles/overview from another route, you’ll continue to see the templates from the previous route until the articles finish loading, and then, boom, suddenly all the templates for articles/overview load.

如果导航到articles/overview,并且在ArticlesRoute#model中,返回了一个AJAX查询承诺,来加载需要花费较长时间才能完成加载的文章集合。在这其间,UI并不会有任何关于在做什么的实际性反馈;如果通过页面刷新进入到该页面,UI将会一直是空的,因为这时还没有完成进入路由,也没有显示任何模板;如果是从其他的路由进入articles/overview,那么会一直停留在之前路由渲染的模板,直到所有文章加载完成,这个时候articles/overview的所有模板才会被渲染,才可见。

So, how can we provide some visual feedback during the transition?

那么,在这个过渡的过程中该如何添加一些反馈信息呢?

The loading event

loading事件

Before going into detail about loading substates, it’s important to understand the behavior of the loading event.

在深入讨论加载中子状态之前,理解loading事件的行为非常重要。

The Ember Router allows you to return promises from the various beforeModel/model/afterModel hooks in the course of a transition (described here). These promises pause the transition until they fulfill, at which point the transition will resume. If you return a promise from one of these hooks, and it doesn’t immediately resolve, a loading event will be fired on that route and bubble upward to ApplicationRoute. For example:

Ember路由允许通过beforeModel/model/afterModel各种钩子在一个过渡的过程中返回承诺(详细介绍)。这些承诺在其被履行前会将过渡暂停。如果在其中一个钩子中返回了承诺,并且这个承诺没有理解解决,那么loading事件就会在该路由被触发,并且一直向上冒泡到ApplicationRoute。例如:

  1. App.Router.map(function() {
  2. this.resource('foo', function() { // -> FooRoute
  3. this.route('slowModel'); // -> FooSlowModelRoute
  4. });
  5. });
  6. App.FooSlowModelRoute = Ember.Route.extend({
  7. model: function() {
  8. return somePromiseThatTakesAWhileToResolve();
  9. },
  10. actions: {
  11. loading: function(transition, originRoute) {
  12. // displayLoadingSpinner();
  13. // Return true to bubble this event to `FooRoute`
  14. // or `ApplicationRoute`.
  15. return true;
  16. }
  17. }
  18. });

If FooRoute#model had returned the slow promise, the loading event would have fired on FooRoute (and not FooSlowModelRoute).

如果FooSlowModelRoute返回了一个较慢的承诺,那么loading事件就会在FooRoute上被触发(而不是FooSlowModelRoute上)。

The default implementation of the loading event

loading事件的缺省实现

So already, you have a hook to allow you to configure loading behavior in a hierarchical manner. But in addition to this, Ember provides a default implementation of the loading handler that implements the following loading substate behavior we’ve been alluding to.

之前已经介绍了可以使用一种层次化的结构来配置loading行为。此外,Ember还提供了一种实现了如下所示的缺省的loading处理器。

  1. App.Router.map(function() {
  2. this.resource('foo', function() { // -> FooRoute
  3. this.resource('foo.bar', function() { // -> FooBarRoute
  4. this.route('baz'); // -> FooBarBazRoute
  5. });
  6. });
  7. });

If a route with the path foo.bar.baz returns a promise that doesn’t immediately resolve, Ember will try to find a loading route in the hierarchy above foo.bar.baz that it can transition into, starting with foo.bar.baz‘s sibling:

如果foo.bar.baz路由返回了一个不会立即履行的承诺,Ember会在foo.bar.baz的层次结构中查找可以过渡的loading路由,查找路径为:

  1. foo.bar.loading
  2. foo.loading
  3. loading

Ember will find a loading route at the above location if either a) a Route subclass has been defined for such a route, e.g.

Ember会在上述的loading路由地址找一个路由,a) 该路由可能是如下所示定义的一个路由的子类:

  1. App.FooBarLoadingRoute
  2. App.FooLoadingRoute
  3. App.LoadingRoute

or b) a properly-named loading template has been found, e.g.

b) 又或者是一个按照一定规则进行命名的loading模板:

  1. foo/bar/loading
  2. foo/loading
  3. loading

During a slow asynchronous transition, Ember will transition into the first loading sub-state/route that it finds, if one exists. The intermediate transition into the loading substate happens immediately (synchronously), the URL won’t be updated, and, unlike other transitions that happen while another asynchronous transition is active, the currently active async transition won’t be aborted.

在一个较慢的异步过渡过程中,如果存在loading子状态/路由,Ember会先过渡到第一个找到的。这个到loading子状态的中间状态的过渡会立即发生(同步的),URL也不会发生改变。与其他在当另一个异步过渡活动时发生的过渡不同,该情况下的活动异步过渡不会被取消。

After transitioning into a loading substate, the corresponding template for that substate, if present, will be rendered into the main outlet of the parent route, e.g. foo.bar.loading‘s template would render into foo.bar‘s outlet. (This isn’t particular to loading routes; all routes behave this way by default.)

当进入一个loading子状态过渡后,对应该子状态的模板如果存在的话,会被渲染到父路由的主插口(outlet),例如foo.bar.loading模板会被渲染到foo.bar的插口中。(loading路由并不是特例,所有路由都是按照这种方式工作的。)

Once the main async transition into foo.bar.baz completes, the loading substate will be exited, its template torn down, foo.bar.baz will be entered, and its templates rendered.

foo.bar.baz主异步过渡完成时,会退出loading子状态,渲染的模板也会被移除,并进入foo.bar.baz,渲染其模板。

Eager vs. Lazy Async Transitions

渴望型 VS 延迟型异步过渡

Loading substates are optional, but if you provide one, you are essentially telling Ember that you want this async transition to be “eager”; in the absence of destination route loading substates, the router will “lazily” remain on the pre-transition route while all of the destination routes’ promises resolve, and only fully transition to the destination route (and renders its templates, etc.) once the transition is complete. But once you provide a destination route loading substate, you are opting into an “eager” transition, which is to say that, unlike the “lazy” default, you will eagerly exit the source routes (and tear down their templates, etc) in order to transition into this substate. URLs always update immediately unless the transition was aborted or redirected within the same run loop.

loading子状态都是可选的,如果提供了loading子状态,那么就表示强调了希望异步过渡为“渴望型”的。在缺少目标路由的loading子状态时,路由将依然停留在之前的过渡路由,知道所有目标路由的承诺得到履行,知会在过渡完全完成是一次性过渡到目标路由(渲染模板等)。但是如果提供了一个目标路由的loading子状态,那么就选择了“渴望型”过渡,这就表明与默认的“延迟型”不同,会首先退出当前路由(清除其模板等),并过渡到loading子状态。除非过渡被取消或者在同一运行循环中被重定向,否则URL都会理解更新。

This has implications on error handling, i.e. when a transition into another route fails, a lazy transition will (by default) just remain on the previous route, whereas an eager transition will have already left the pre-transition route to enter a loading substate.

这里也暗含了一个错误处理,例如,当过渡到一个路由失败时,延迟过渡(默认)会依然停留在原路由,而一个渴望过渡早就离开了之前的状态路由进入到loading子状态中了。

error substates

error子状态

Ember provides an analogous approach to loading events/substates in the case of errors encountered during a transition.

Ember为过渡提供了一种与loading事件/子状态类似的错误处理方法。

  1. App.Router.map(function() {
  2. this.resource('articles', function() { // -> ArticlesRoute
  3. this.route('overview'); // -> ArticlesOverviewRoute
  4. });
  5. });

If ArticlesOverviewRoute#model returns a promise that rejects (because, for instance, the server returned an error, or the user isn’t logged in, etc.), an error event will fire on ArticlesOverviewRoute and bubble upward. This error event can be handled and used to display an error message, redirect to a login page, etc., but similar to how the default loading event handlers are implemented, the default error handlers will look for an appropriate error substate to enter, if one can be found.

如果ArticlesOverviewRoute返回一个被拒绝的承诺(可能因为服务器端返回一个错误,又或者时用户没有登录等等),一个error事件将在ArticlesOverviewRoute被触发,并向上冒泡。可以使用这个error事件来处理并显示一个错误消息,例如重定向到登录页面等。与loading事件处理器实现类似,缺省的error事件处理器也会进入一个子状态来完成处理。

For instance, an error thrown or rejecting promise returned from ArticlesOverviewRoute#model (or beforeModel or afterModel) will look for:

例如,在ArticlesOverviewRoute#model(或beforeModelafterModel)中抛出了一个异常或者返回了一个被拒绝的承诺,那么会按照以下方式进行查找错误处理:

  1. Either ArticlesErrorRoute or articles/error template
  2. Either ErrorRoute or error template

  3. ArticlesErrorRoute路由或者articles/error模板

  4. ErrorRoute路由或者error模板

If one of the above is found, the router will immediately transition into that substate (without updating the URL). The “reason” for the error (i.e. the exception thrown or the promise reject value) will be passed to that error state as its model.

如果之上任意一个被找到,路由将立即过渡到该子状态(不更新URL),错误的“原因”(例如抛出的异常或拒绝的承诺)会作为model传递给error子状态。

If no viable error substates can be found, an error message will be logged.

如果没有找到可以访问的error子状态,那么一条错误消息会在控制台中输出。

The only way in which loading/error substate resolution differs is that error events will continue to bubble above a transition’s pivot route.

loading/error子状态处理的唯一区别是,error从过渡的中心路由开始向上冒泡。

error substates with dynamic segments

带动态段的error子状态

Routes with dynamic segments are often mapped to a mental model of “two separate levels.” Take for example:

带动态段的路由通常映射到一个模型的两个不同的层面。例如:

  1. App.Router.map(function() {
  2. this.resource('foo', {path: '/foo/:id'}, function() {
  3. this.route('baz');
  4. });
  5. });
  6. App.FooRoute = Ember.Route.extend({
  7. model: function(params) {
  8. return new Ember.RSVP.Promise(function(resolve, reject) {
  9. reject("Error");
  10. });
  11. }
  12. });

In the URL hierarchy you would visit /foo/12 which would result in rendering the foo template into the application template’s outlet. In the event of an error while attempting to load the foo route you would also render the top-level error template into the application template’s outlet. This is intentionally parallel behavior as the foo route is never successfully entered. In order to create a foo scope for errors and render foo/error into foo‘s outlet you would need to split the dynamic segment:

在URL层次中,访问/foo/12将会导致将foo模板渲染到application模板的outlet处。当尝试加载一个foo路由发生一个错误事件,会将顶层的error模板渲染到application模板的outlet。这感觉就好像foo路由就从未正确进入过一般。为了创建一个foo范围的错误信息,并渲染foo/errorfoooutlet中,那么需要将动态段分离:

  1. App.Router.map(function() {
  2. this.resource('foo', {path: '/foo'}, function() {
  3. this.resource('elem', {path: ':id'}, function() {
  4. this.route('baz');
  5. });
  6. });
  7. });

Example JSBin

Legacy LoadingRoute

遗留的LoadingRoute

Previous versions of Ember (somewhat inadvertently) allowed you to define a global LoadingRoute which would be activated whenever a slow promise was encountered during a transition and exited upon completion of the transition. Because the loading template rendered as a top-level view and not within an outlet, it could be used for little more than displaying a loading spinner during slow transitions. Loading events/substates give you far more control, but if you’d like to emulate something similar to the legacy LoadingRoute behavior, you could do as follows:

之前的Ember版本(有些不慎)支持通过定义一个全局的LoadingRoute,该路由将在过渡遇到一个较慢的承诺或者完全退出一个过渡时被激活。因为loading模板作为顶层的视图来渲染,并没有放入到一个插口中,那么在这里处理可以显示一个加载中的指示器外几乎不能做其他的事情。与此相比较,loading事件/子状态提供了更强的控制力,如果希望模拟与遗留的LoadingRoute类似的行为,可以按照如下的例子来实现:

  1. App.LoadingView = Ember.View.extend({
  2. templateName: 'global-loading',
  3. elementId: 'global-loading'
  4. });
  5. App.ApplicationRoute = Ember.Route.extend({
  6. actions: {
  7. loading: function() {
  8. var view = this.container.lookup('view:loading').append();
  9. this.router.one('didTransition', view, 'destroy');
  10. }
  11. }
  12. });

Example JSBin

This will, like the legacy LoadingRoute, append a top-level view when the router goes into a loading state, and tear down the view once the transition finishes.

JSBin示例

上例实现了一个与LoadingRoute类型的行为,当路由进入一个loading状态时,在顶层添加了一个视图,并在完成过渡时删除加入的视图。