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

JavaScript异步函数调用问题:fetch后tapplace函数时灵时不灵

问题分析与解决方案

问题核心

调用API设置filetype变量后,在tapplace函数中使用该变量,但函数有时无法正常运行,本质是fetch异步特性和组件注册逻辑错误共同导致的:

  • 异步时序问题:fetch是异步操作,若AFRAME的tap-place-cursor组件在请求完成前就被初始化,filetype还未被赋值,会导致组件逻辑出错。
  • 重复注册组件:每次遍历到type="content"的资源就调用tapplace(),而AFRAME不允许重复注册同名组件,后续调用会直接报错,组件无法正常生效。
  • 全局变量覆盖:filetype是全局变量,多次遍历资源会覆盖其值,最终组件拿到的可能不是预期的文件类型。

修复后的代码方案

var query = window.location.search.replace("?", "");
var slug_url = query.split("?id=");
slug_url = `${slug_url}`.replace("id=", "");

// 先获取正确的filetype,再注册组件
fetch("https://api.morkshub.xyz/api/get_node/" + slug_url)
  .then(response => response.json())
  .then(data => {
    let filetype = '';
    // 找到第一个content类型的资源(可根据需求调整为处理所有资源)
    const contentAsset = data.data
      .flatMap(obj => obj.assets)
      .find(newObj => newObj.type === "content");
    
    if (contentAsset) {
      const gltfUrl = `url(${ApiAssetUrl + contentAsset.assets})`;
      content = ApiAssetUrl + contentAsset.assets;
      if(gltfUrl.includes(".glb")){
        filetype = 'model'
      }else if(gltfUrl.includes(".jpg") || gltfUrl.includes(".png")){
        filetype = 'image';
      }else if(gltfUrl.includes(".mp4") || gltfUrl.includes(".m4v")){
        filetype = 'video'
      }
      console.log(filetype);
      // 只注册一次组件,把filetype作为参数传入
      registerTapPlaceCursor(filetype);
    }
  })
  .catch(err => console.error("API请求失败:", err));

// 封装组件注册函数,接收filetype参数
function registerTapPlaceCursor(filetype){
  const tapPlaceCursorComponent = {
    init() {
      console.log("hellooooooo" , filetype)
      let model;
      if(filetype === "image"){
        model = document.getElementById('entityimage');
      } else if(filetype === "video"){
        model = document.getElementById('entityvideo');
      } else{
        model = document.getElementById('model');
      }
      this.model = model;
      this.raycaster = new THREE.Raycaster();
      this.camera = document.getElementById('camera');
      this.threeCamera = this.camera.getObject3D('camera');
      this.ground = document.getElementById('ground');
      this.cursor = document.getElementById('cursor');
      let hasPlacedModel = false;
      this.rayOrigin = new THREE.Vector2(0, 0);
      this.cursorLocation = new THREE.Vector3(0, 0, 0);
      
      this.el.sceneEl.addEventListener('click', (event) => {
        if (!hasPlacedModel) {
          hasPlacedModel = true;
          this.model.setAttribute('position', this.el.object3D.position);
          this.model.setAttribute('visible', 'true');
          this.model.play();
          this.cursor.parentNode.removeChild(this.cursor);
          this.camera.setAttribute('raycaster', 'objects: .cantap');
        }
      });
    },
    tick() {
      this.raycaster.setFromCamera(this.rayOrigin, this.threeCamera);
      const intersects = this.raycaster.intersectObject(this.ground.object3D, true);
      if (intersects.length > 0) {
        const [intersect] = intersects;
        this.cursorLocation = intersect.point;
      }
      this.el.object3D.position.y = 0.1;
      this.el.object3D.position.lerp(this.cursorLocation, 0.4);
      this.el.object3D.rotation.y = this.threeCamera.rotation.y;
    },
  };
  // 注册前先检查组件是否已存在,避免报错
  if (!AFRAME.components['tap-place-cursor']) {
    AFRAME.registerComponent('tap-place-cursor', tapPlaceCursorComponent);
  }
}

关键优化点

  • 确保异步完成后注册组件:等待API请求完成并确定filetype后,再注册AFRAME组件,避免组件初始化时变量未赋值。
  • 避免重复注册组件:只在获取到有效filetype后注册一次组件,注册前检查组件是否已存在,防止报错。
  • 明确资源获取逻辑:使用flatMap和find找到目标资源,避免多次遍历覆盖全局变量,确保拿到预期的文件类型。
  • 消除全局变量依赖:将filetype作为参数传入组件注册函数,避免全局变量带来的不可预测性。

内容的提问来源于stack exchange,提问作者Vipul Sharma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 18:42:50