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
相关产品推荐
相关产品推荐

