You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ember模型计算属性延迟导致模板未渲染相关信息的问题

解决Ember模型中异步计算属性导致模板不显示的问题

嘿,这个问题我之前也碰到过!核心就是异步计算的时序和模板渲染的时机没对上——模板先渲染完了,你的距离计算还在后台跑,等计算完赋值的时候,模板已经没再更新了(或者初始是空值直接没渲染那部分)。咱们先看看你代码里的几个小问题,再一步步解决:

首先,你把milesAway定义成了attr('string'),但其实它是通过异步逻辑计算出来的,不是后端返回的属性;另外,你的计算逻辑放在了googleLocation这个computed里,但这个computed没有被模板引用,而且它的异步操作完成后才给milesAway赋值,这时候模板已经渲染完了,自然看不到内容。

接下来给你几个具体的解决思路:


思路1:把milesAway改成异步计算属性

既然milesAway是异步计算的结果,直接把它定义成异步计算属性,让Ember自动处理异步状态,模板也能感知到它的变化。

修改后的模型代码大概是这样的:

import DS from 'ember-data';
import attr from 'ember-data/attr';
import { hasMany } from 'ember-data/relationships';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default DS.Model.extend({
  featuredImages: hasMany('contentful-asset'),
  featured: attr('boolean'),
  name: attr('string'),
  slug: attr('string'),
  rating: attr('number'),
  location: attr('string'),
  latitude: attr('string'),
  longitude: attr('string'),
  bodyOne: attr('string'),
  
  googleMapsApi: service(),

  // 把milesAway改成异步计算属性
  milesAway: computed('location', async function() {
    const userLocation = 'Hard Coded Test Address';
    const endLocation = this.get('location');
    const google = await this.get('googleMapsApi.google');

    return new Promise((resolve, reject) => {
      const distanceMatrixService = new google.maps.DistanceMatrixService();
      distanceMatrixService.getDistanceMatrix({
        origins: [userLocation],
        destinations: [endLocation],
        travelMode: google.maps.TravelMode.DRIVING,
        unitSystem: google.maps.UnitSystem.IMPERIAL,
        avoidHighways: false,
        avoidTolls: false
      }, (response, status) => {
        if (status !== google.maps.DistanceMatrixStatus.OK) {
          this.toggleProperty('errorState');
          reject();
          return;
        }
        const element = response.rows[0].elements[0];
        if (element.status === "ZERO_RESULTS") {
          this.toggleProperty('errorState');
          reject();
          return;
        }
        resolve(element.distance.text);
      });
    });
  })
});

然后模板里怎么用?如果是Ember 3.16+,可以用{{await}}助手来等待异步计算完成:

{{#if (await model.milesAway)}}
  距离:{{await model.milesAway}}
{{else}}
  正在计算距离...
{{/if}}

如果是旧版本,可以用{{#if model.milesAway.isPending}}来判断加载状态:

{{#if model.milesAway.isPending}}
  正在计算距离...
{{else if model.milesAway.isRejected}}
  距离计算失败
{{else}}
  距离:{{model.milesAway.value}}
{{/if}}

思路2:用Tracked属性+异步方法(Ember Octane+推荐)

如果你的项目是Ember Octane及以上版本,用Tracked属性会更简洁,代码更直观:

import DS from 'ember-data';
import attr from 'ember-data/attr';
import { hasMany } from 'ember-data/relationships';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency'; // 可以用ember-concurrency处理异步任务

export default class WineryModel extends DS.Model {
  @hasMany('contentful-asset') featuredImages;
  @attr('boolean') featured;
  @attr('string') name;
  @attr('string') slug;
  @attr('number') rating;
  @attr('string') location;
  @attr('string') latitude;
  @attr('string') longitude;
  @attr('string') bodyOne;

  @service googleMapsApi;
  @tracked milesAway;
  @tracked errorState = false;

  // 用ember-concurrency的task来处理异步计算,自动管理状态
  @task *calculateMilesAway() {
    this.errorState = false;
    const userLocation = 'Hard Coded Test Address';
    const endLocation = this.location;
    const google = yield this.googleMapsApi.google;

    return new Promise((resolve, reject) => {
      const distanceMatrixService = new google.maps.DistanceMatrixService();
      distanceMatrixService.getDistanceMatrix({
        origins: [userLocation],
        destinations: [endLocation],
        travelMode: google.maps.TravelMode.DRIVING,
        unitSystem: google.maps.UnitSystem.IMPERIAL,
        avoidHighways: false,
        avoidTolls: false
      }, (response, status) => {
        if (status !== google.maps.DistanceMatrixStatus.OK) {
          this.errorState = true;
          reject();
          return;
        }
        const element = response.rows[0].elements[0];
        if (element.status === "ZERO_RESULTS") {
          this.errorState = true;
          reject();
          return;
        }
        this.milesAway = element.distance.text;
        resolve();
      });
    });
  }

  // 模型加载后自动触发计算
  init() {
    super.init(...arguments);
    this.calculateMilesAway.perform();
  }
}

模板里就可以直接根据tracked属性的状态来渲染:

{{#if errorState}}
  距离计算失败
{{else if (not milesAway)}}
  正在计算距离...
{{else}}
  距离:{{milesAway}}
{{/if}}

思路3:优化计算逻辑,避免重复计算

另外,你可以给计算属性加上依赖项,比如location,这样只有当酒庄的位置变化时,才会重新计算距离,而不是每次都执行。比如在思路1里,computed的依赖就是'location',这样就不会做无用的重复计算。

还有,把用户位置也改成可追踪的属性,比如从服务里获取,而不是硬编码,这样用户位置变化时也能重新计算。


总结一下,核心就是:

  1. 让milesAway的定义符合它的异步计算属性本质,而不是普通的模型属性;
  2. 模板里要处理加载中、成功、失败三种状态,避免因为初始值为空而不显示内容;
  3. 用Ember提供的异步处理工具(比如async computed、ember-concurrency)来管理异步逻辑,让代码更健壮。

内容的提问来源于stack exchange,提问作者Chris Johnson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 03:52:31