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

Okta批量删用户:Promise未等待完成致最后错误未写入CSV

问题分析与解决方案

你的问题核心在于异步流程没有正确对齐:原代码里的deleteUserTimeout Promise在2秒后就直接resolve了,完全没等待Okta的API请求(激活、删除)完成,导致最后一次API请求的错误还没被收集到record数组,外层的finally就已经执行了CSV写入操作,所以最后一条错误没出现在输出文件里。另外,全局的currentError变量也存在被后续请求覆盖的风险。

接下来一步步修复你的代码:

1. 修复异步流程,确保等待API请求完成

把每次用户删除的逻辑包装成一个完整的Promise,让await真正等待API请求(包括错误处理)全部完成,再继续下一次循环。同时把延迟放在请求完成之后,避免速率超限。

2. 避免全局变量污染,每个请求独立处理错误

去掉全局的currentError,在每个请求的catch块里直接创建错误记录并推入数组,避免被后续请求覆盖。

3. 修正错误的Promise reject逻辑

原代码里在finally里调用reject()会导致Promise异常,这里不需要,我们只需要正常收集错误即可。

修改后的完整代码

"use strict";
const okta = require("@okta/okta-sdk-nodejs");
const csv = require("csv-parser");
const fs = require("fs");
const createCsvWriter = require("csv-writer").createObjectCsvWriter;

// 格式化时间戳作为日志文件名
const timeRun = new Date()
  .toISOString()
  .replace(/T/, " ")
  .replace(/\..+/, "")
  .replace(/:/g, ".");

// 初始化CSV写入器
const csvWriter = createCsvWriter({
  path: `errorLog-${timeRun}.csv`,
  header: [
    { id: "error", title: "Error" },
    { id: "row", title: "Row" },
  ],
});

const errorRecords = [];

// Okta客户端配置
const client = new okta.Client({
  orgUrl: "https://xxxxxxxx.oktapreview.com",
  token: "xxxxxxxxxxxxxxxxxxxxxxx",
});

// 读取待删除用户列表的函数
const getUsersToDelete = () => new Promise((resolve, reject) => {
  const users = [];
  fs.createReadStream("testImport.csv")
    .pipe(csv())
    .on("data", (row) => users.push(row))
    .on("end", () => resolve(users))
    .on("error", (err) => reject(err));
});

// 单个用户删除+延迟的函数
const deleteUserWithRateLimit = async (userRow, iteration) => {
  try {
    const user = await client.getUser(userRow.email);
    await user.deactivate();
    console.log(`Iteration ${iteration}: User ${userRow.email} has been deactivated`);
    await user.delete();
    console.log(`Iteration ${iteration}: User ${userRow.email} has been deleted`);
  } catch (error) {
    // 直接创建错误记录并推入数组,避免全局变量
    const errorRecord = { 
      error: error.message, 
      row: JSON.stringify(userRow) // 这里可以根据需要调整row的内容,原代码写的是"row",建议存入实际用户行数据
    };
    console.error(`Iteration ${iteration}: Error`, errorRecord);
    errorRecords.push(errorRecord);
  } finally {
    // 延迟2秒,控制请求速率
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
};

// 主流程
(async () => {
  try {
    const usersToDelete = await getUsersToDelete();
    console.log(`Loaded ${usersToDelete.length} users to delete`);

    let iteration = 1;
    // 用for...of循环保证用户按CSV顺序处理(原代码pop会倒序处理)
    for (const userRow of usersToDelete) {
      await deleteUserWithRateLimit(userRow, iteration);
      iteration++;
    }

    // 所有请求完成后,写入错误日志
    await csvWriter.writeRecords(errorRecords);
    console.log(`Error log written to errorLog-${timeRun}.csv`);
  } catch (err) {
    console.error("Fatal error:", err.message);
    // 如果读取CSV失败,也写入错误日志
    await csvWriter.writeRecords([{ error: err.message, row: "CSV read failure" }]);
  }
})();

关键修改点说明

  • 异步流程对齐:deleteUserWithRateLimit函数把API请求和延迟都包装成异步操作,await这个函数时会等待所有步骤完成,确保错误被收集后再继续下一次循环。
  • 去掉全局变量:每个错误记录在catch块里直接创建并推入数组,避免了全局变量被覆盖的问题。
  • 更清晰的循环逻辑:用for...of替代while+pop,保证用户按CSV里的顺序处理(原代码用pop会倒序处理)。
  • 错误边界处理:主流程用try...catch包裹,捕获读取CSV等致命错误,确保异常情况下也能写入日志。
  • 正确的延迟时机:把延迟放在请求完成之后,确保两次请求之间间隔2秒,严格控制速率。

这样修改后,所有错误(包括最后一个)都会被正确收集并写入CSV文件,同时也保证了请求速率不会超限。

内容的提问来源于stack exchange,提问作者Jake Durell

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 07:47:33