JavaScript抢占式后台任务调度通用方案及IndexedDB加载场景问询
实现抢占式后台任务调度方案
要解决这个问题,核心是让Web Worker支持任务优先级调整和正在执行任务的中断,同时主线程和Worker保持状态同步。下面是一套完整的JavaScript实现方案,分主线程和Web Worker两部分来说:
一、主线程逻辑:缓存管理与用户交互
主线程负责维护缓存状态、响应用户交互,以及和Worker的通信。我们用Map来存储缓存和加载状态,确保快速查询:
// 主线程:缓存存储,key是表名,value是对应数据 const tableCache = new Map(); // 记录每个表的加载状态:'idle'(未开始) | 'loading'(加载中) | 'loaded'(已完成) const tableLoadStatus = new Map(); // 创建Web Worker实例 const dbWorker = new Worker('db-worker.js'); // 初始化需要批量加载的表列表 const initialTables = ['user', 'order', 'product', 'category']; // 初始化每个表的状态为未开始 initialTables.forEach(table => tableLoadStatus.set(table, 'idle')); // 向Worker发送初始加载任务 dbWorker.postMessage({ type: 'init-tasks', tables: initialTables }); // 监听Worker发来的消息,更新本地状态和缓存 dbWorker.onmessage = (e) => { const { type, table, data } = e.data; switch (type) { case 'table-loaded': tableCache.set(table, data); tableLoadStatus.set(table, 'loaded'); console.log(`✅ 表${table}已加载至缓存`); // 这里可以添加UI更新逻辑,比如隐藏加载提示 break; case 'task-started': tableLoadStatus.set(table, 'loading'); console.log(`🔄 开始加载表${table}`); break; case 'task-canceled': tableLoadStatus.set(table, 'idle'); console.log(`⏹️ 表${table}加载任务已取消`); break; } }; // 用户点击查看表的处理函数 function handleViewTable(tableName) { const status = tableLoadStatus.get(tableName); if (status === 'loaded') { // 直接从缓存取数据渲染 renderTable(tableCache.get(tableName)); return; } if (status === 'loading') { console.log(`表${tableName}正在加载中,请稍候`); return; } // 未开始加载,发送抢占请求给Worker dbWorker.postMessage({ type: 'prioritize-task', table: tableName }); // 显示加载提示 showLoadingIndicator(tableName); } // 辅助函数:渲染表数据到UI function renderTable(data) { // 替换成你的UI渲染逻辑 console.log('渲染数据:', data); } // 辅助函数:显示加载提示 function showLoadingIndicator(table) { console.log(`正在优先加载表${table}...`); }
二、Web Worker逻辑:任务调度与IndexedDB操作
Worker负责维护任务队列、处理IndexedDB请求,并且支持中断正在执行的任务。这里关键是用AbortController来中断异步的IndexedDB请求,同时实现优先级队列:
// db-worker.js let taskQueue = []; // 当前正在执行的任务的控制器,用于中断请求 let currentTaskController = null; // 当前正在处理的表名 let currentProcessingTable = null; // IndexedDB实例 let dbInstance = null; // 初始化IndexedDB连接 async function initIndexedDB() { return new Promise((resolve, reject) => { const request = indexedDB.open('MyAppDatabase', 1); // 如果数据库版本升级,创建对应的objectStore(根据你的实际表结构调整) request.onupgradeneeded = (e) => { dbInstance = e.target.result; // 假设初始化时需要创建的表和主线程初始列表一致 initialTables.forEach(table => { if (!dbInstance.objectStoreNames.contains(table)) { dbInstance.createObjectStore(table); } }); }; request.onsuccess = (e) => { dbInstance = e.target.result; resolve(); }; request.onerror = (e) => { reject(new Error(`IndexedDB初始化失败: ${e.target.error}`)); }; }); } // 加载单个表的数据,支持通过AbortController中断 async function loadSingleTable(tableName, abortController) { return new Promise((resolve, reject) => { // 如果控制器已经中断,直接返回错误 if (abortController.signal.aborted) { reject(new Error('任务已被中断')); return; } // 开启只读事务 const transaction = dbInstance.transaction(tableName, 'readonly'); const objectStore = transaction.objectStore(tableName); // 获取表中所有数据 const getAllRequest = objectStore.getAll(); // 监听中断信号,触发时中止IndexedDB请求 abortController.signal.addEventListener('abort', () => { getAllRequest.abort(); }); getAllRequest.onsuccess = () => { resolve(getAllRequest.result); }; getAllRequest.onerror = (e) => { reject(new Error(`加载表${tableName}失败: ${e.target.error}`)); }; }); } // 处理任务队列的循环函数 async function processTasks() { while (taskQueue.length > 0) { // 取出队列最前面的任务(高优先级任务会被插入到队列头部) const currentTask = taskQueue.shift(); currentProcessingTable = currentTask.table; currentTaskController = new AbortController(); // 通知主线程任务开始 self.postMessage({ type: 'task-started', table: currentTask.table }); try { // 加载表数据 const tableData = await loadSingleTable(currentTask.table, currentTaskController); // 通知主线程加载完成 self.postMessage({ type: 'table-loaded', table: currentTask.table, data: tableData }); } catch (error) { if (error.name === 'AbortError') { // 任务被中断,通知主线程 self.postMessage({ type: 'task-canceled', table: currentTask.table }); } else { console.error(`加载表${currentTask.table}出错:`, error); } } finally { // 重置当前任务状态 currentTaskController = null; currentProcessingTable = null; } } } // 监听主线程发来的消息 self.onmessage = async (e) => { const { type, tables, table } = e.data; switch (type) { case 'init-tasks': // 初始化IndexedDB await initIndexedDB(); // 将初始任务添加到队列(低优先级,放在队列尾部) taskQueue = tables.map(t => ({ table: t, priority: 'low' })); // 开始处理任务队列 processTasks(); break; case 'prioritize-task': // 先检查任务是否已经在队列中,避免重复添加 const existingTaskIndex = taskQueue.findIndex(task => task.table === table); if (existingTaskIndex !== -1) { // 移除队列中已有的该任务 taskQueue.splice(existingTaskIndex, 1); } // 如果当前正在处理的就是目标表,无需操作 if (currentProcessingTable === table) { return; } // 如果当前有正在执行的任务,中断它 if (currentTaskController) { currentTaskController.abort(); } // 将高优先级任务插入到队列头部 taskQueue.unshift({ table, priority: 'high' }); // 如果当前没有在处理任务,启动任务处理循环 if (!currentTaskController) { processTasks(); } break; } };
三、关键技术点说明
AbortController中断异步请求:
浏览器原生的AbortController可以用来中断支持signal参数的异步操作(包括IndexedDB请求、fetch等)。当需要抢占任务时,调用currentTaskController.abort()就能中断正在进行的IndexedDB请求,让Worker立刻处理高优先级任务。优先级任务队列:
当收到主线程的抢占请求时,我们会把目标任务从队列中移除(如果存在),然后插入到队列头部,确保下一个被处理的就是这个高优先级任务。状态同步机制:
主线程和Worker通过消息传递同步每个表的加载状态,避免重复请求或者状态不一致的问题。比如Worker开始加载、加载完成、任务取消时都会通知主线程更新本地状态。任务去重:
在添加高优先级任务前,先检查队列中是否已经存在该任务,如果有就移除后再添加到头部,避免队列中出现重复任务。
内容的提问来源于stack exchange,提问作者prmph
相关产品推荐
相关产品推荐

