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

使用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);
    }
}
解决方案

你的代码存在几个核心问题导致列无法一次性创建完成:

  1. 异步操作未正确等待:所有列创建和视图添加操作都用了.then()链式调用,但没有用await等待完成,导致后续立即查询列列表时,新列还没创建完成,触发不必要的递归调用。
  2. setTimeout的滥用:给VideoUrl列加了5秒延迟,不仅没必要,还会导致该列创建滞后于其他列,进一步触发递归。
  3. 混合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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 10:05:33