在Ember中返回模型属性的承诺

时间:2014-06-03 18:12:49

标签: javascript ember.js ember-data promise

我正在研究habit tracking app。它是用Ember编写的,由CouchDB支持。

在主屏幕上,我想显示一个习惯列表以及每次记录事件的最后时间。以下是我的模型:

App.Habit = DS.Model.extend( {
  type: DS.attr('string', { defaultValue: 'habit' } ),
  name: DS.attr( 'string' ),
  color: DS.attr( 'string' ),
  events: DS.hasMany( 'event', { async: true, defaultValue: [] } ),
  style: function() {
    return 'background-color: %@'.fmt( this.get( 'color' ) )
  }.property( 'color' ),
  lastTime: function() {
    return this
      .get( 'events' )
      .then( function( events ) {
        var times = events.map( function( e ) {
          return e.get( 'time' )
        } )
        return times.length == 0 ? undefined : times.sort()[0]
      } )
  }.property( 'events' ),
} )

App.Event = DS.Model.extend( {
  type: DS.attr('string', { defaultValue: 'event' } ),
  time: DS.attr( 'date' ),
  habit: DS.belongsTo( 'habit' )
} )

请注意lastTime的{​​{1}}属性会返回一个承诺。当我没有承诺时,数组的长度为0.我的把手模板看起来像:

Habit

<script type="text/x-handlebars" id="habits"> <div id="habits"> {{#each}} <div class="row" {{action 'createEvent' id}}> <div class="col-xs-1 color" {{bind-attr style=style}}></div> <div class="col-xs-8">{{name}}</div> <div class="col-xs-3" data-role="timer">{{format-time-numeric lastTime}}</div> </div> {{/each}} </div> </script> 助手只是:

format-time-numeric

传递给片刻的Ember.Handlebars.registerBoundHelper( 'format-time-numeric', function( time ) { return moment( time ).format( 'YYYY/M/D @ H:mm' ) } ) 是一个承诺,并呈现为一堆time。如果我从NaN传回承诺,则会将其呈现为format-time

5 个答案:

答案 0 :(得分:2)

在这种情况下你可以使用reduceComputed属性:

  maxTime: Ember.reduceComputed('events.@each.time', {
    initialValue: -Infinity,
    addedItem: function(accValue, event) { 
      return Math.max(accValue, event.get('time'));
    },
    removedItem: function(accValue, event) { 
      if (event.get('time') < accValue ) {
        return accValue;
      }
    }
  })

答案 1 :(得分:0)

答案 2 :(得分:0)

不要从

返回承诺
lastTime

相反,请将其写为:

lastTime: function() {
    var times =  this.get('events').map(function(event) {
                      return event.get('time');
                  });      
    return times.length == 0 ? undefined : times.sort()[0]
  } )
}.observes('events').property(),

答案 3 :(得分:0)

你的模型的逻辑是有缺陷的。它正在观察“事件”的变化,但也通过承诺获得“事件”。如果“事件”已经解决,那么您不需要承诺。如果“事件”没有,那么你就无法观察它。

模型通常不是Promises的地方 - 您的视图不了解如何显示Promise。 Promise通常在Routes中解析,结果存储在Models中。我会重新建议将它移动到Route或者如果不可能有一些动作或观察者在模型中设置属性。

选项1:如果事件已经在内存中:

见Hrishi回答

选项2:如果事件不在内存中且需要Promise:

这样的事情可能有用:

lastTime: null,

然后在你的控制器中有一个类似于此的动作(或使用其他一些触发类似代码的方式):

lastTimeAction: function() {
   var self = this;
   this.get( 'events' ).then( function( events ) {
      var times = events.map( function( e ) {
         return e.get( 'time' );
      });
      self.set('lastTime', times.length == 0 ? undefined : times.sort()[0]);
  });
}

答案 4 :(得分:0)

引用您的评论:

  

我认为Ember应该等待承诺完成并将结果发送到模板。

那是......一个棘手的问题。简单地说,不,它不会等待。我能想到id 等待的唯一一次是在模型钩子中。如果您从beforeModelmodelafterModel返回承诺,路由器将在继续之前暂停。但是,这不适用于控制器或模板,并且在您获得模型的异步关系时不适用于您的情况。

因此,一个可能的解决方案是暂停路由器,直到加载events关系。你的路由器里有这样的东西:

model: function(params) {
    return this.store.find('habit', params.habit_id).then(function(habit) {
        // Return another promise, to continue pausing the router
        return habit.get('lastTime');
    });
}

这将暂停路由器(而不是渲染模板),直到您的lastTime属性完全计算。

另一个选项(我建议)是将lastTime属性异步插入模板中。如果更改了属性的计算方式,则可以在获取events时更新模板:

lastTime: function() {
    var events = this.get('events');

    if (events.length <= 0) {
        return 0; // Or some other default
    }

    return events.mapBy('time').sort()[0];
}.property('events.@each.time')

因此,您的模板最初会显示0,但会在events准备好时以实时更新。

正如Michael Benin所提到的,第三个选项是将模型上的events关系设置为同步关系。您可以立即计算您的属性,但每当您获取events模型时,您必须在JSON中包含所有habit模型。 (不推荐。拥抱异步。)