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

JS批处理循环中仅首次迭代执行prune逻辑的实现方法

需求说明

现有批量保存逻辑通过forEach循环分批次调用saveCandidatesData({ screeningNumbers, pruneScreeningNumbers: prune })实现,用来降低单次请求负载。需要调整逻辑满足:

  • 所有批次都必须执行saveCandidatesData完成数据保存
  • pruneScreeningNumbers对应的清理逻辑仅在第一个批次执行时触发

以3个批次为例,执行逻辑应该为:

  • 第1次:传入批次1的screeningNumbers + pruneScreeningNumbers参数,执行清理+保存
  • 第2次:仅传入批次2的screeningNumbers,不执行清理逻辑,直接保存
  • 第3次:仅传入批次3的screeningNumbers,不执行清理逻辑,直接保存
待修改原始代码
const screeningNumbersCalculations = async (data, batches) => {
  console.log('batches: ', batches);
  const results = {};
  const mappedDateId = dateIdMap(data);

  batches.map(b => {
    b.forEach(t => {
      const matchingStatusesTime = Object.values(mappedDateId).filter(s => {
        return new Date(s.statusFrom) < endOfDay(t) && (s.statusTo === null || new Date(t) <= new Date(s.statusTo));
      });

      matchingStatusesTime.forEach(ms => {
        const label = assignLabel(ms.statusType, ms.eligible);
        const key = `${t}${ms.studyId}${ms.siteId}${label}`;
        if (!results[key]) {
          results[key] = {
            days: [t],
            studyId: ms.studyId,
            siteId: ms.siteId,
            screeningStage: 'SELF_ASSESSMENT',
            label,
            total: 0,
          };
        }
        results[key].total += 1;
      });
      const screeningNumbers = Object.values(results);

      const cnt = saveCandidatesData({ screeningNumbers, pruneScreeningNumbers: prune });

      log.info('Replay screening numbers completed in %d', cnt);
    });
    return Promise.resolve();
  });
};

附逻辑理解用简化可运行版本(未接入保存函数):

const mockData = [{
    id: "C10-TBX4",
    studyId: "TBX4",
    siteId: "USA-1",
    statusType: "INCOMPLETE",
    statusFrom: "2020-12-05T01:00:00.000Z",
    statusTo: "2020-12-05T02:00:00.000Z"
  },
  {
    id: "C10-TBX4",
    studyId: "TBX4",
    siteId: "USA-1",
    statusType: "DROPOUT_PRESCREENER",
    eligible: true,
    statusFrom: "2020-12-05T02:00:00.000Z",
    statusTo: "2020-12-05T03:00:00.000Z"
  },
  {
    id: "C10-TBX4",
    studyId: "TBX4",
    siteId: "USA-1",
    statusType: "INCOMPLETE",
    statusFrom: "2020-12-05T03:00:00.000Z",
    statusTo: null
  }
];

function endOfDay(time) {
  const date = new Date(time);
  date.setUTCHours(23, 59, 59, 999);
  return date;
}

const assignLabel = (statusType, eligible) => {
  if (statusType === "INCOMPLETE") return "pending";
  if (statusType === "REJECTED_PRESCREENER") return "ineligible";
  if (statusType === "DROPOUT_PRESCREENER" && eligible) return "eligible";
  if (statusType === "DROPOUT_PRESCREENER" && !eligible) return "abandoned";
  return "completed";
};

// 日期根据查询范围生成,示例中仅取1天
const batches = ["2020-12-05T00:00:00.000Z"];

const screeningNumbersCalculations = () => {
  const results = {};
  mockData.sort((a, b) => endOfDay(a).getTime() - endOfDay(b).getTime());
  const dateidMap = mockData.reduce((accum, item) => {
    accum[`${endOfDay(item).getTime()}.${item.id}`] = item;
    return accum;
  }, {});
  console.log(dateidMap)
  batches.forEach((t) => {
    const matchingStatusesTime = Object.values(dateidMap).filter((s) => {
      return (
        new Date(s.statusFrom) < endOfDay(t) &&
        (s.statusTo === null || new Date(t) <= new Date(s.statusTo))
      );
    });

    matchingStatusesTime.forEach((ms) => {
      const key = `${t}${ms.studyId}${ms.siteId}${assignLabel(
        ms.statusType,
        ms.eligible
      )}`;
      if (!results[key]) {
        results[key] = {
          days: [t],
          studyId: ms.studyId,
          siteId: ms.siteId,
          screeningStage: "SELF_ASSESSMENT",
          label: assignLabel(ms.statusType, ms.eligible),
          total: 0
        };
      }
      results[key].total++;
    });
  });
  return Object.values(results);
};
const screeningNumbers = screeningNumbersCalculations();
console.log(JSON.stringify(screeningNumbers, null, 2));
实现方案

核心逻辑是在循环外层加一个布尔标记位,记录当前是否为第一个批次:

  1. 初始标记设为true,代表还没处理第一个批次
  2. 每次调用保存函数前判断标记:如果是第一个批次,就传入pruneScreeningNumbers参数,执行完保存后把标记设为false
  3. 后续批次判断标记为false,就不传prune参数,跳过清理逻辑

另外原代码用Array.map遍历异步逻辑不会等待执行完成,容易出现保存顺序错乱,改成for...of循环搭配await保证批次按顺序执行。

修改后完整代码:

const screeningNumbersCalculations = async (data, batches) => {
  console.log('batches: ', batches);
  const results = {};
  const mappedDateId = dateIdMap(data);
  // 标记是否为第一个处理批次
  let isFirstBatch = true;

  for (const b of batches) {
    for (const t of b) {
      const matchingStatusesTime = Object.values(mappedDateId).filter(s => {
        return new Date(s.statusFrom) < endOfDay(t) && (s.statusTo === null || new Date(t) <= new Date(s.statusTo));
      });

      matchingStatusesTime.forEach(ms => {
        const label = assignLabel(ms.statusType, ms.eligible);
        const key = `${t}${ms.studyId}${ms.siteId}${label}`;
        if (!results[key]) {
          results[key] = {
            days: [t],
            studyId: ms.studyId,
            siteId: ms.siteId,
            screeningStage: 'SELF_ASSESSMENT',
            label,
            total: 0,
          };
        }
        results[key].total += 1;
      });
      const screeningNumbers = Object.values(results);

      // 按批次动态构造保存参数
      const saveParams = { screeningNumbers };
      if (isFirstBatch) {
        saveParams.pruneScreeningNumbers = prune;
        // 第一个批次处理完后更新标记,后续不再执行prune逻辑
        isFirstBatch = false;
      }
      // 异步保存加await保证执行顺序
      const cnt = await saveCandidatesData(saveParams);

      log.info('Replay screening numbers completed in %d', cnt);
    }
  }
};

该方案不需要修改saveCandidatesData内部逻辑,只要函数内部判断pruneScreeningNumbers参数不存在时跳过清理逻辑即可,符合绝大多数工具函数的参数判断规则。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 23:33:18