使用pnp/sp创建SPFx列表及列时部分列无法一次性生成的问题
问题描述
在基于React的SPFx解决方案中使用@pnp/sp创建SharePoint列表及列时,部分列无法一次性创建完成,必须重新运行代码才能创建成功,求解决。
环境配置
- node 14.15.4
- @microsoft/generator-sharepoint@1.14.0
- gulp@4.0.2
- npm@6.14.10
- yo@4.3.0
- @pnp/sp@2.11.0
相关代码片段
import { sp } from '@pnp/sp'; import "@pnp/sp/webs"; import "@pnp/sp/lists"; import "@pnp/sp/items"; import "@pnp/sp/fields"; import "@pnp/sp/views"; import { UrlFieldFormatType } from "@pnp/sp/fields"; export const trainingsList = async () => { try { const listEnsureResult = await sp.web.lists.ensure("Trainings", "Trainings", 100, false, { Hidden: false }); if (listEnsureResult.created) { return listEnsureResult; } } catch (error) { console.log("Error in Trainings List", error); } } export const trainingsFields = async () => { const courseList = await sp.web.lists.getByTitle("Course")(); try { await sp.web.lists.getByTitle("Trainings").fields.select("*").get().then(async (res) => { let listData = []; res.forEach(ele => { listData.push(ele.InternalName); }); if (listData.indexOf("VideoUrl") == -1) { setTimeout(() => { sp.web.lists.getByTitle("Trainings").fields.addUrl("VideoUrl", UrlFieldFormatType.Hyperlink).then(() => { sp.web.lists.getByTitle("Trainings").defaultView.fields.add("VideoUrl"); }) }, 5000); } if (listData.indexOf("Walkthrough") == -1) { sp.web.lists.getByTitle("Trainings").fields.addMultilineText("Walkthrough").then(() => { sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Walkthrough"); }); } if (listData.indexOf("Status") == -1) { sp.web.lists.getByTitle("Trainings").fields.addBoolean("Status").then(() => { sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Status"); }); } if (listData.indexOf("TrainingDuration") == -1) { sp.web.lists.getByTitle("Trainings").fields.addNumber("TrainingDuration").then(() => { sp.web.lists.getByTitle("Trainings").defaultView.fields.add("TrainingDuration"); }); } if (listData.indexOf("Course") == -1) { sp.web.lists.getByTitle("Trainings").fields.addLookup("Course", courseList.Id, "Title").then(() => { sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Course"); }); } await sp.web.lists.getByTitle("Trainings").fields.select("*").get().then(async (resListData) => { let listColumnData = []; resListData.forEach(ele => { listColumnData.push(ele.InternalName); }); console.log("listColumnData...", listColumnData); if (listColumnData.indexOf("VideoUrl") != -1 && listColumnData.indexOf("Walkthrough") != -1 && listColumnData.indexOf("Status") != -1 && listColumnData.indexOf("TrainingDuration") != -1 && listColumnData.indexOf("Course") != -1) { console.log("Inside IF....."); } else { console.log("Inside ELSE....."); await trainingsFields(); } }).catch((err) => { console.log("Error in trainings fields creation", err); }); console.log("Trainings fields created successfully!"); }).catch((err) => { console.log("Error in Trainings fields", err); }); } catch (error) { console.log("Error in Trainings fields", error); } }
解决方案
你的代码存在几个核心问题导致列无法一次性创建完成:
- 异步操作未正确等待:所有列创建和视图添加操作都用了
.then()链式调用,但没有用await等待完成,导致后续立即查询列列表时,新列还没创建完成,触发不必要的递归调用。 - setTimeout的滥用:给VideoUrl列加了5秒延迟,不仅没必要,还会导致该列创建滞后于其他列,进一步触发递归。
- 混合async/await和.then():代码同时使用两种异步写法,逻辑混乱,容易导致时序问题。
修复后的代码如下:
import { sp } from '@pnp/sp'; import "@pnp/sp/webs"; import "@pnp/sp/lists"; import "@pnp/sp/items"; import "@pnp/sp/fields"; import "@pnp/sp/views"; import { UrlFieldFormatType } from "@pnp/sp/fields"; export const trainingsList = async () => { try { const listEnsureResult = await sp.web.lists.ensure("Trainings", "Trainings", 100, false, { Hidden: false }); return listEnsureResult; } catch (error) { console.log("Error in Trainings List", error); throw error; } } export const trainingsFields = async () => { try { // 获取Course列表信息 const courseList = await sp.web.lists.getByTitle("Course")(); // 获取Trainings列表的现有列 const existingFields = await sp.web.lists.getByTitle("Trainings").fields.select("InternalName").get(); const existingInternalNames = existingFields.map(ele => ele.InternalName); const promises = []; // 创建VideoUrl列并添加到默认视图 if (!existingInternalNames.includes("VideoUrl")) { const addFieldPromise = sp.web.lists.getByTitle("Trainings").fields.addUrl("VideoUrl", UrlFieldFormatType.Hyperlink) .then(() => sp.web.lists.getByTitle("Trainings").defaultView.fields.add("VideoUrl")); promises.push(addFieldPromise); } // 创建Walkthrough列并添加到默认视图 if (!existingInternalNames.includes("Walkthrough")) { const addFieldPromise = sp.web.lists.getByTitle("Trainings").fields.addMultilineText("Walkthrough") .then(() => sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Walkthrough")); promises.push(addFieldPromise); } // 创建Status列并添加到默认视图 if (!existingInternalNames.includes("Status")) { const addFieldPromise = sp.web.lists.getByTitle("Trainings").fields.addBoolean("Status") .then(() => sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Status")); promises.push(addFieldPromise); } // 创建TrainingDuration列并添加到默认视图 if (!existingInternalNames.includes("TrainingDuration")) { const addFieldPromise = sp.web.lists.getByTitle("Trainings").fields.addNumber("TrainingDuration") .then(() => sp.web.lists.getByTitle("Trainings").defaultView.fields.add("TrainingDuration")); promises.push(addFieldPromise); } // 创建Course查找列并添加到默认视图 if (!existingInternalNames.includes("Course")) { const addFieldPromise = sp.web.lists.getByTitle("Trainings").fields.addLookup("Course", courseList.Id, "Title") .then(() => sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Course")); promises.push(addFieldPromise); } // 等待所有列创建和视图添加操作完成 await Promise.all(promises); // 验证所有列是否已创建 const finalFields = await sp.web.lists.getByTitle("Trainings").fields.select("InternalName").get(); const finalInternalNames = finalFields.map(ele => ele.InternalName); const requiredFields = ["VideoUrl", "Walkthrough", "Status", "TrainingDuration", "Course"]; const allCreated = requiredFields.every(field => finalInternalNames.includes(field)); if (allCreated) { console.log("所有Trainings列已成功创建并添加到视图!"); return true; } else { console.log("部分列未创建成功,重试一次"); // 等待1秒再重试,避免无限递归 await new Promise(resolve => setTimeout(resolve, 1000)); return trainingsFields(); } } catch (error) { console.log("Error in Trainings fields", error); throw error; } }
修复要点说明
- 统一使用async/await:摒弃
.then()链式调用,改用await等待异步操作完成,保证时序正确。 - 批量异步操作:用
Promise.all()并行执行所有列创建操作,提高效率,同时确保所有操作完成后再进行后续验证。 - 移除不必要的setTimeout:SharePoint字段创建是即时的,不需要额外延迟,移除后避免时序混乱。
- 限制递归次数:添加单次重试逻辑,防止因未知错误导致无限循环。
- 错误抛出:在catch块中抛出错误,方便上层逻辑处理异常情况。
内容的提问来源于stack exchange,提问作者Hiren Changela
相关产品推荐
相关产品推荐

