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));
实现方案
核心逻辑是在循环外层加一个布尔标记位,记录当前是否为第一个批次:
- 初始标记设为
true,代表还没处理第一个批次 - 每次调用保存函数前判断标记:如果是第一个批次,就传入
pruneScreeningNumbers参数,执行完保存后把标记设为false - 后续批次判断标记为
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
相关产品推荐
相关产品推荐

