
本文讲解如何改造嵌套异步循环逻辑,确保「先完成一个资源某月的同步,再执行下一个资源/下个月」,避免并发混乱;核心是将任务扁平化为有序队列,并用 async/await 串行执行。
本文讲解如何改造嵌套异步循环逻辑,确保「先完成一个资源某月的同步,再执行下一个资源/下个月」,避免并发混乱;核心是将任务扁平化为有序队列,并用 `async/await` 串行执行。
在日历同步等场景中,服务端常限制请求频率或数据量(如“每次仅接受单用户单月数据”),此时必须严格控制执行顺序:不能让多个 $.post 并发发出,也不能跳过月份或资源。原始代码使用递归回调处理资源列表,但外层 for 循环仍会立即推进到下个月——因为 loopThroughResources 是异步的,循环不等待其完成。
解决的关键在于 解耦「任务生成」与「任务执行」:
- 先计算所有待执行的原子任务(resourceId + 日期区间),形成扁平化队列;
-
再按序逐个
await执行每个任务,天然保证串行。
以下为重构后的完整实现(基于现代 Promise 和 async/await):
✅ 步骤一:生成日期区间(按月切分)
function getFirstAndLastDayOfMonth(year, month) {
const start = new Date(year, month, 1);
const end = new Date(year, month + 1, 0); // 上月末日即本月最后一天
return {
start: start.toISOString().split('T')[0],
end: end.toISOString().split('T')[0]
};
}
function getDateRanges(startDate, endDate) {
const start = new Date(startDate);
const end = new Date(endDate);
const ranges = [];
let current = new Date(start);
// 按月遍历:从起始月的第一天开始,逐月推进
while (current <h3>✅ 步骤二:组合所有同步任务(资源 × 月份)</h3><pre class="brush:php;toolbar:false;">function getSyncJobs(resourceIds, startDate, endDate) {
const ranges = getDateRanges(startDate, endDate);
const jobs = [];
for (const resourceId of resourceIds) {
for (const { start, end } of ranges) {
jobs.push({ resourceId, startDate: start, endDate: end });
}
}
return jobs; // 形如 [{id:'A',s:'2023-12-27',e:'2023-12-31'}, ...]
}
✅ 步骤三:串行执行每个任务(关键!)
// 将 $.post 封装为 Promise(推荐使用 jQuery 3.2+ 的 $.post 返回 Promise)
function syncResource(job) {
return $.post("sync.php", {
resourceId: job.resourceId,
startDate: job.startDate,
endDate: job.endDate
}).then(response => {
$("#exchange_output").append(
`✅ Sync ${resourceNames.get(job.resourceId)} from ${job.startDate} to ${job.endDate}: ${response}<br>`
);
}).catch(err => {
$("#exchange_output").append(
`❌ Failed for ${job.resourceId}: ${err.statusText || 'Unknown error'}<br>`
);
});
}
// 主执行函数:按序 await 每个任务
async function runSync(resourceIds, startDate, endDate) {
const jobs = getSyncJobs(resourceIds, startDate, endDate);
console.log(`Total ${jobs.length} sync jobs queued.`);
try {
for (const job of jobs) {
await syncResource(job); // 阻塞直到当前请求完成
// 可选:添加微延迟防服务端限流
// await new Promise(resolve => setTimeout(resolve, 100));
}
$("#start_exchange")
.text("Synchroniseren")
.removeAttr("disabled")
.css("cursor", "pointer");
$("#exchange_output").append("<br>? All synchronization completed.<br>");
} catch (error) {
console.error("Sync interrupted:", error);
$("#exchange_output").append(`<br>⚠️ Sync stopped due to error: ${error.message}<br>`);
}
}
⚠️ 注意事项与最佳实践
-
不要混用回调与
async/await:原始代码中loopThroughResources使用递归回调,易导致作用域污染和调试困难;统一用Promise链或await更可靠。 -
错误处理必须显式:
await遇到 rejected Promise 会抛出异常,务必用try/catch包裹整个循环,避免中断后状态不可知。 -
UI 反馈建议增强:可在
for循环内动态更新进度条或当前任务提示,提升用户体验。 -
性能权衡:串行执行虽安全,但总耗时 = 单次请求 × 任务数。若服务端允许有限并发(如最多 3 个并行),可用
Promise.allSettled()分批执行,但需额外管理批次逻辑。
通过此方案,示例中的 A/B/C 同步将严格按以下顺序执行:
A: 2023-12-27 → 2023-12-31 B: 2023-12-27 → 2023-12-31 C: 2023-12-27 → 2023-12-31 A: 2024-01-01 → 2024-01-31 ...(依此类推)
彻底消除竞态,保障数据一致性与服务端兼容性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











