Electron中jQuery操作在Promise.all完成前提前执行的问题
问题
在Electron和Node环境中编写了一段异步代码,在jQuery each循环中执行,预期所有异步操作完成后再执行后续逻辑:
$(async() => { if (await window.electronAPI.delete_items(project.name)) { var _count = 0; $.each(project.items, function(i, item) { var promises = item.subitems.map(async function(subitem) { return await window.electronAPI.update_item(subitem); }); Promise.all(promises).then(function() { _count++; if (_count >= project.items.length) { $("#overlay").css("display", "none"); console.log("Done"); } }); }); } });
整体逻辑运行正常,但$("#overlay").css("display", "none");会立即执行,而console.log("Done");能正确在所有item更新完成后运行。请问如何避免jQuery操作过早执行?
补充完整代码:
// index.js app.whenReady().then( () => { ... ipcMain.handle( "delete_all_cached_images", delete_all_cached_images ); ipcMain.handle( "update_cached_image", update_cached_image ); /* Create the window */ createWindow(); app.on( "activate", () => { /* Fix MacOS multi window bug */ if( BrowserWindow.getAllWindows().length === 0 ) createWindow(); } ); } ); async function update_cached_image( e, project_name, image_type, image_size, image_name, image_data ) { /* Create a new blank image */ let image = new Jimp( image_size, image_size, function ( err, image ) { if( err ) return false; /* Map the pixels correct */ var _image_data = Array.from( { length: image_size }, () => Array.from( { length: image_size }, () => undefined ) ); for( var row_sel = 0; row_sel < image_size; row_sel++ ) for( var col_sel = 0; col_sel < image_size; col_sel++ ) _image_data[ col_sel ][ row_sel ] = image_data.data[ row_sel ][ col_sel ]; /* Loop through each pixel to add to the image */ _image_data.forEach( ( row, y ) => { row.forEach( ( colour, x ) => { /* If we have a transparent pixel, don't add it to the image */ if( ( colour == "" ) || ( colour == null ) ) image.setPixelColor( Jimp.rgbaToInt( 0, 0, 0, 0 ), parseInt( x ), parseInt( y ) ); else image.setPixelColor( Jimp.cssColorToHex( "#" + colour ), parseInt( x ), parseInt( y ) ); } ); } ); /* Resize to a nice large size */ image.resize( 512, 512 , Jimp.RESIZE_NEAREST_NEIGHBOR ); /* Save the image to project directory */ image.write( path.join( __dirname, "projects", project_name, "cache", image_type, image_name + ".png" ), ( err ) => { if( err ) return false; } ); } ); return true; } // preload.js const { contextBridge, ipcRenderer } = require( "electron" ); contextBridge.exposeInMainWorld( "electronAPI", { ... delete_all_cached_images: ( project_name ) => ipcRenderer.invoke( "delete_all_cached_images", project_name ), update_cached_image: ( project_name, image_type, image_size, image_name, image_data ) => ipcRenderer.invoke( "update_cached_image", project_name, image_type, image_size, image_name, image_data ) } ); // renderer.js function update_cached_images() { $( async () => { /* First delete all old cached images */ if( await window.electronAPI.delete_all_cached_images( project.name ) ) { var ti = 0; Promise.all( project.textures.map( g_texture => { return Promise.all( g_texture.textures.map( texture => { return window.electronAPI.update_cached_image( project.name, "textures", 8, ( g_texture.name + "_" + ti++ ), texture ); } ) ); } ) ).then( () => { $("#overlay").css("display", "none"); console.log("Done"); } ).catch(err => { console.log(err); // add something to handle/process errors here } ); } else { show_error( "Error caching project textures." ); } } ); }
原因分析
计数逻辑的不可靠性:
原始代码用_count变量统计完成的异步任务,但$.each是同步循环,会瞬间启动所有Promise.all任务。如果某组子任务意外快速完成(比如空数组、无实际异步操作),会导致_count提前达到阈值,触发overlay隐藏操作。主进程异步逻辑的漏洞:
update_cached_image标记为async,但内部Jimp的创建、写入都是回调式异步操作,函数直接返回true,没有等待这些异步操作完成。这会让前端的ipcRenderer.invoke提前收到返回值,误以为任务已完成。
解决方案
1. 重构前端异步逻辑,用Promise.all统一管理
放弃计数方式,直接收集所有层级的Promise,等待全部完成后再执行后续操作:
$(async() => { if (await window.electronAPI.delete_items(project.name)) { // 收集所有子任务的Promise const allPromises = project.items.map(item => { return Promise.all(item.subitems.map(subitem => { return window.electronAPI.update_item(subitem); })); }); // 等待所有任务完成 await Promise.all(allPromises); $("#overlay").css("display", "none"); console.log("Done"); } });
2. 修复主进程update_cached_image的异步逻辑
将Jimp的回调API包装为Promise,确保函数真正等待图片处理和写入完成:
async function update_cached_image(e, project_name, image_type, image_size, image_name, image_data) { try { // 包装Jimp创建操作 const image = await new Promise((resolve, reject) => { new Jimp(image_size, image_size, (err, img) => { if (err) reject(err); else resolve(img); }); }); // 像素映射逻辑不变 const _image_data = Array.from({ length: image_size }, () => Array.from({ length: image_size }, () => undefined)); for (let row_sel = 0; row_sel < image_size; row_sel++) { for (let col_sel = 0; col_sel < image_size; col_sel++) { _image_data[col_sel][row_sel] = image_data.data[row_sel][col_sel]; } } _image_data.forEach((row, y) => { row.forEach((colour, x) => { if (!colour) { image.setPixelColor(Jimp.rgbaToInt(0, 0, 0, 0), parseInt(x), parseInt(y)); } else { image.setPixelColor(Jimp.cssColorToHex("#" + colour), parseInt(x), parseInt(y)); } }); }); image.resize(512, 512, Jimp.RESIZE_NEAREST_NEIGHBOR); // 包装图片写入操作 await new Promise((resolve, reject) => { const savePath = path.join(__dirname, "projects", project_name, "cache", image_type, image_name + ".png"); image.write(savePath, (err) => { if (err) reject(err); else resolve(); }); }); return true; } catch (err) { console.error(err); return false; } }
3. 优化renderer.js逻辑(针对补充代码)
保持Promise.all层级结构,同时用await替代then回调,让逻辑更清晰:
function update_cached_images() { $(async () => { if (await window.electronAPI.delete_all_cached_images(project.name)) { let ti = 0; try { await Promise.all(project.textures.map(g_texture => { return Promise.all(g_texture.textures.map(texture => { return window.electronAPI.update_cached_image( project.name, "textures", 8, `${g_texture.name}_${ti++}`, texture ); })); })); $("#overlay").css("display", "none"); console.log("Done"); } catch (err) { console.log(err); show_error("Error caching project textures."); } } else { show_error("Error caching project textures."); } }); }
关键要点
- 避免用计数变量跟踪异步任务,
Promise.all是更可靠的批量异步管理方式。 - 主进程中所有回调式异步操作必须包装为Promise,确保IPC调用的返回值真正对应任务完成状态。
- 优先使用
await替代then回调,简化异步代码的时序逻辑。
内容的提问来源于stack exchange,提问作者GSephElec
相关产品推荐
相关产品推荐

