
本文详解为何在使用 async/await 与 Mongoose 保存数据时,模型字段(如 title、summary、artworks)意外存为 {} 空对象,并提供正确异步赋值、类型匹配与结构化建模的完整解决方案。
本文详解为何在使用 `async/await` 与 mongoose 保存数据时,模型字段(如 `title`、`summary`、`artworks`)意外存为 `{}` 空对象,并提供正确异步赋值、类型匹配与结构化建模的完整解决方案。
在使用 Mongoose 向 mongodb 插入数据时,若模型字段被持久化为 {}(空对象)而非预期的字符串或数组,根本原因几乎总是对 promise 对象进行了非等待的直接操作——尤其是误将未 await 的异步函数调用结果传入 json.Stringify() 或直接赋值给 Schema 字段。
回顾原始代码中的关键问题:
const Bloodborne = new Game({ title: JSON.stringify(fetchGameData([0]).then(value => console.log(value))), summary: JSON.stringify(fetchGameData([1]).then(value => console.log(value))), artworks: JSON.stringify(fetchGameData([2]).then(value => console.log(value))) });
这段代码存在两个严重错误:
- fetchGameData([0]) 返回的是一个 Promise,而非实际数据;
- .then(…) 返回的仍是 Promise(且未处理 rejection),而 JSON.stringify(Promise) 的结果恒为 “{}” —— 这正是你在 MongoDB 中看到空对象的根源。
✅ 正确做法是:在构造 Game 实例前,先 await 获取真实数据,再按 Schema 类型赋值。同时,需注意 fetchGameData 函数的设计缺陷:它本应返回单个游戏对象的结构化数据,但当前实现却将 name、summary、artworks 扁平压入数组,导致索引访问语义混乱(如 fetchGameData([0]) 实际取的是 name 字符串,而 fetchGameData([2]) 取的是 artworks 数组 —— 但 artworks 本身是数组,不应再被 JSON.stringify() 包裹后存入 Array 类型字段)。
以下是修复后的专业实践方案:
✅ 步骤一:重构 fetchGameData,返回结构化对象
const fetchGameData = async () => { const config = { url: 'https://api.igdb.com/v4/games/', headers: { 'Client-ID': 'SECRET', 'Authorization': 'SECRET' }, data: 'fields name, summary, artworks; where name = "Bloodborne";' }; try { const response = await axios.post(config.url, config.data, config); const game = response.data[0]; // 假设返回至少一个匹配项 return { title: game?.name || '', summary: game?.summary || '', artworks: Array.isArray(game?.artworks) ? game.artworks : [] }; } catch (err) { console.error('Failed to fetch game data:', err.response?.status, err.message); throw err; } };
✅ 步骤二:seedDB 中正确 await 并赋值(禁止在构造器内 await)
const seedDB = async () => { await connectDB(); await Game.deleteMany({}); try { const { title, summary, artworks } = await fetchGameData(); // ✅ 解构获取真实值 const Bloodborne = new Game({ title, // String → 直接赋值字符串 summary, // String → 直接赋值字符串 artworks // Array → 直接赋值数组(无需 JSON.stringify!) }); await Bloodborne.save(); console.log('✅ Game "Bloodborne" seeded successfully.'); } catch (err) { console.error('❌ Seeding failed:', err); } }; seedDB();
✅ 步骤三:确保 Mongoose Schema 类型定义精准
// models/game.js const gameSchema = new mongoose.Schema({ title: { type: String, required: true }, summary: { type: String, default: '' }, artworks: [{ id: Number, image_id: String, width: Number, height: Number, url: String }] // 明确声明为对象数组,提升类型安全与查询能力 }); module.exports = mongoose.model('Game', gameSchema);
⚠️ 关键注意事项
- 永远不要对 Promise 调用 JSON.stringify():它不会解析 Promise,只会序列化其空对象外壳;
- Mongoose 自动处理 JavaScript 原生类型:String、Array、Object 等无需手动 JSON.stringify();仅当需存储 JSON 字符串字段(如 metadata: String)时才使用;
- 避免在 Schema 字段赋值中嵌套 .then() 或未 await 的异步调用:所有异步数据必须在 new Model({…}) 之前完成解析;
- 启用 Mongoose 严格模式与校验:在连接时添加 strict: true 和 runValidators: true,可提前捕获类型不匹配错误。
通过以上重构,你的 title 将以字符串形式、artworks 以数组形式准确写入 MongoDB,彻底规避 {} 空对象陷阱,同时提升代码可维护性与数据一致性。