
本文详解为何在使用 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,彻底规避 {} 空对象陷阱,同时提升代码可维护性与数据一致性。










