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

GLTF模型加载时.then()未等待所有Promise完成,出现迭代报错

异步加载GLTF模型时Promise提前resolve导致的问题分析与修复

问题描述

使用async函数加载多个GLTF模型,通过.then()遍历结果并添加到场景,原本运行正常,但移除一个大体积模型后,.then()未等待所有Promise完成就执行,控制台报错:'variant.components is not iterable'。以下是相关代码:

加载器函数

async function modelLoader(source, loadingManager) {
    const gltfLoader = new GLTFLoader(loadingManager);
    const promises = [];
    for (let i = 0; i < source.length; i++) {
      promises.push(
        new Promise((resolve) => {
          const model = {}
          gltfLoader.load(source[i].file, (gltf) => {
            const children = [...gltf.scene.children];
            console.log(children);
              model.components = children;
            resolve(model);
          });
          if (source[i].type === 'baseModel' || (source[i].type === 'variant' && source[i].viewPoints.file !== 'none')) {
            gltfLoader.load(source[i].viewPoints.file, (gltf) => {
              const viewPoints = [...gltf.scene.children];
                model.viewPoints = viewPoints;
              resolve(model);
            });
          } else if (source[i].type === 'baseModel' || (source[i].type === 'variant' && source[i].viewPoints.file === 'none')) {
            model.viewPoints = 'none';
          } else if (source[i].type === 'land') {
            gltfLoader.load(source[i].buildingLine, (gltf) => {
              const buildingLine = [...gltf.scene.children]
              model.buildingLine = buildingLine
              resolve(model)
            })
          }
        })
      );
    }
    return await Promise.all(promises);
  }

调用代码

const allModels = modelLoader(source, loadingManager)

allModels.then(result => {
    for (const variant of result) {
        if (variant.type === 'baseModel') {
            const house = new THREE.Group()
            for (const components of variant.components) {
                house.add(components)
            }       
        }                       
    }
})     

问题根源

  1. 同一个Promise被多次resolve:对于baseModel或符合条件的variant类型,主模型加载和viewPoints加载的回调里都调用了resolve(model)。哪个异步请求先完成,就会先触发Promise resolve,此时如果viewPoints加载更快,model.components还未被赋值,后续遍历就会因variant.components不存在报错。
  2. 逻辑判断冗余:第二个else if重复判断了source[i].type === 'baseModel',导致逻辑混乱,可能出现不符合预期的属性赋值。
  3. 部分场景未等待所有加载完成:比如land类型的模型,仅在buildingLine加载完成后resolve,但主模型加载可能还未完成,同样会导致model.components缺失。

修复方案

重构每个模型的加载逻辑,将当前模型的所有异步加载任务包装成子Promise数组,通过Promise.all等待所有任务完成后再resolve模型对象,确保所有属性都被正确赋值。

修复后的加载器函数

async function modelLoader(source, loadingManager) {
    const gltfLoader = new GLTFLoader(loadingManager);
    const promises = [];

    for (const item of source) {
        const modelPromise = new Promise(async (resolve) => {
            const model = {};
            const subPromises = [];

            // 加载主模型
            const loadMainModel = new Promise((res) => {
                gltfLoader.load(item.file, (gltf) => {
                    model.components = [...gltf.scene.children];
                    res();
                });
            });
            subPromises.push(loadMainModel);

            // 处理不同类型的额外加载任务
            if (item.type === 'baseModel' || item.type === 'variant') {
                if (item.viewPoints.file !== 'none') {
                    const loadViewPoints = new Promise((res) => {
                        gltfLoader.load(item.viewPoints.file, (gltf) => {
                            model.viewPoints = [...gltf.scene.children];
                            res();
                        });
                    });
                    subPromises.push(loadViewPoints);
                } else {
                    model.viewPoints = 'none';
                }
            } else if (item.type === 'land') {
                const loadBuildingLine = new Promise((res) => {
                    gltfLoader.load(item.buildingLine, (gltf) => {
                        model.buildingLine = [...gltf.scene.children];
                        res();
                    });
                });
                subPromises.push(loadBuildingLine);
            }

            // 等待当前模型的所有加载任务完成后再resolve
            await Promise.all(subPromises);
            resolve(model);
        });
        promises.push(modelPromise);
    }

    return Promise.all(promises);
}

关键修复点

  • 为每个模型创建子Promise数组,收集主模型、viewPoints、buildingLine等所有异步加载任务。
  • 使用Promise.all等待当前模型的所有加载任务完成,确保model的所有属性都被赋值后再resolve。
  • 简化逻辑判断,移除冗余的条件判断,让代码逻辑更清晰。
  • 确保无论哪种模型类型,都要等主模型加载完成后才resolve,避免components属性缺失。

额外优化建议

  • 给gltfLoader.load添加错误回调,处理加载失败的情况,避免Promise永远处于pending状态:
    gltfLoader.load(item.file, (gltf) => {
        model.components = [...gltf.scene.children];
        res();
    }, undefined, (err) => {
        console.error('主模型加载失败:', err);
        res(); // 或reject(err),根据业务需求处理
    });
    
  • 在调用.then()时添加catch捕获错误,便于调试:
    allModels.then(result => {
        // 处理逻辑
    }).catch(err => {
        console.error('模型加载出错:', err);
    });
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 13:22:51