未包含nutrients时无法向DynamoDB推送数据的求助
问题描述
- 表单数据推送到DynamoDB时,包含
nutrients字段时正常,缺失该字段则推送失败,报错Error RequestAbortedError: Request aborted - 需求:除
recipe_index(脚本顶部查询自动生成)和item_type(固定为{S: 'recipe'})外,其他任意字段缺失都能正常完成推送 - 排查情况:未发现代码中有依赖
nutrients存在的逻辑,调整简化代码、谷歌搜索均未找到有效解决方法,刚接触DynamoDB、JS和React
相关代码
export function SaveCreateButton(props){ // Load the AWS SDK for Node.js var AWS = require('aws-sdk'); // Create DynamoDB service object var ddb = new AWS.DynamoDB({apiVersion: '2012-08-10'}); // Query for number of items in the table through GSI var params = { ExpressionAttributeValues: { ':type' : {S: 'recipe'}, }, KeyConditionExpression: 'item_type = :type', TableName: 'recipes', IndexName: 'item_type-index', }; const [data, setData] = useState([]); useEffect(() => { ddb.query(params, function (err, data) { if (data) { console.log('length: ', data['Items'].length) setData(data['Items'].length); } else { console.log(err); } }); }, [props.title]); var navigate = useNavigate(); const saveNew = () => { var currentTitle = document.getElementById('title').value var currentYield = document.getElementById('yield').value var currentTime = document.getElementById('time').value var currentPicture = document.getElementById('picture').value var instNodes = document.querySelectorAll('[id=instructions]') var currentInstructions = Array.prototype.map.call(instNodes, function(t) { return {S : t.value}; }); var ingNodes = document.querySelectorAll('[id=ingredients]') var currentIngredients = Array.prototype.map.call(ingNodes, function(t) { return {S : t.value}; }); var nvNodes = document.querySelectorAll('[id=nutrientsValue]') var currentNutrientsValue = Array.prototype.map.call(nvNodes, function(t) { return {S : t.value}; }); var ntNodes = document.querySelectorAll('[id=nutrientsType]') for(const element of nt_nodes){ var regex = /^\s*$|[a-z]|[a-zA-Z]+([A-Z][a-z]+)+$/g if (regex.test(element.value) === false){ element.classList.add('err') alert('"' + element.value + '" must be in camelCase.') return false } } var currentNutrientsType = Array.prototype.map.call(ntNodes, function(t) { return t.value; }); var nutrientsMap = {}; currentNutrientsType.forEach((key, i) => nutrientsMap[key] = currentNutrientsValue[i]); // Call DynamoDB to add the item to the table var params = { TableName: 'recipes', Item: { 'recipe_index': {N: String(data)}, 'title': {S : currentTitle}, 'yields': {S : currentYield}, 'cook_time': {S : currentTime}, 'image': {S: currentPicture}, 'instructions': {L: currentInstructions}, 'ingredients' : {L: currentIngredients}, 'nutrients' : {M: nutrientsMap}, 'item_type' : {S: 'recipe'}, }, }; ddb.putItem(params, function(err, data) { if (err) { console.log("Error", err); } else { console.log("Success", data); } }); // go back to display page and refresh. navigate(currentTitle); window.location.reload(false); } return(<button name={props.name} className={props.className} onClick={saveNew} type="button">Save</button>) }
问题分析
核心原因是DynamoDB不允许存储空的Map类型(M: {}):当没有nutrients数据时,nutrientsMap是空对象,此时将nutrients: {M: nutrientsMap}加入Item会导致请求被DynamoDB中止,触发RequestAbortedError。
另外代码还有2个潜在问题:
- 变量名错误:循环中使用了未定义的
nt_nodes,实际应为ntNodes - 异步操作未处理:
putItem是异步请求,直接调用navigate和reload会导致请求还未完成就跳转页面
修复方案
1. 处理空nutrients字段
仅当nutrientsMap有内容时,才将其加入Item对象:
// 先构建基础Item对象 const item = { 'recipe_index': {N: String(data)}, 'title': {S : currentTitle}, 'yields': {S : currentYield}, 'cook_time': {S : currentTime}, 'image': {S: currentPicture}, 'instructions': {L: currentInstructions}, 'ingredients' : {L: currentIngredients}, 'item_type' : {S: 'recipe'}, }; // 只有nutrientsMap非空时才添加该字段 if (Object.keys(nutrientsMap).length > 0) { item.nutrients = {M: nutrientsMap}; } var params = { TableName: 'recipes', Item: item, };
2. 修复变量名错误
将循环中的nt_nodes改为ntNodes:
for(const element of ntNodes){ var regex = /^\s*$|[a-z]|[a-zA-Z]+([A-Z][a-z]+)+$/g if (regex.test(element.value) === false){ element.classList.add('err') alert(`"${element.value}" must be in camelCase.`) return false } }
3. 优化异步操作逻辑
等待putItem请求完成后再执行页面跳转:
ddb.putItem(params, function(err, data) { if (err) { console.log("Error", err); } else { console.log("Success", data); // 请求成功后再跳转刷新 navigate(currentTitle); window.location.reload(false); } });
4. (可选)改用React受控组件
避免直接操作DOM,用state管理表单值,示例:
// 组件内定义表单状态 const [title, setTitle] = useState(''); // 表单输入框写法 <input id="title" value={title} onChange={(e) => setTitle(e.target.value)} /> // 获取值时直接使用title变量即可 var currentTitle = title;
内容的提问来源于stack exchange,提问作者Art Vandalay
相关产品推荐
相关产品推荐

