Node.js Promise循环中停止Windows服务控制器重复查询问题
解决ColdFusion 10服务启动重试时sc命令重复输出的问题
我来帮你搞定这个问题,同时优化下你的重试逻辑。首先,重复输出的根源是sc模块默认会把所有命令执行过程和结果打印到控制台,每次重试都会触发这些输出;其次,递归Promise的重试方式虽然能跑,但用async/await循环会更清晰可靠。
问题重现
你提到的重复输出示例:
sc \\abnf34873 start ColdFusion 10 Application Server sc \\abnf34873 queryex ColdFusion 10 Application Server SERVICE_NAME: ColdFusion 10 Application Server TYPE : 10 WIN32_OWN_PROCESS STATE : 2 START_PENDING (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x7d0 PID : 0 FLAGS : SERVICE_NAME: ColdFusion 10 Application Server TYPE : 10 WIN32_OWN_PROCESS STATE : 2 START_PENDING (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x7d0 PID : 13772 FLAGS :
解决方案
1. 关闭sc模块的默认日志输出
大多数Windows服务管理的Node.js模块(比如sc-windows)都会提供quiet或silent选项来禁用自动控制台输出。我们只需要在调用sc.query和sc.start时添加这个参数,就能避免重复打印命令和服务状态。
2. 优化重试逻辑
把递归Promise改成async/await循环,不仅代码可读性更高,还能避免递归栈溢出的风险,同时错误处理也更清晰。
完整优化代码
const gulp = require('gulp'); const sc = require('sc-windows'); // 假设你使用的是sc-windows模块 const kill = require('tree-kill'); // 假设kill依赖是tree-kill // 配置常量,方便后续维护 const MAX_RETRIES = 3; const SERVICE_NAME = 'ColdFusion 10 Application Server'; gulp.task('start-coldfusion-service', async function(done) { console.log("Starting ColdFusion service.."); // 全局配置sc的轮询间隔和超时,只需设置一次 sc.pollInterval(30); sc.timeout(60); let retryCount = 0; let serviceStarted = false; while (retryCount < MAX_RETRIES && !serviceStarted) { try { serviceStarted = await tryServiceStart(); if (!serviceStarted) { retryCount++; console.log(`Retry Count: ${retryCount}`); } } catch (err) { retryCount++; console.log(`Problem starting ColdFusion service! Error: ${err.message}`); console.log(`Retrying... (${retryCount}/${MAX_RETRIES})`); } } if (serviceStarted) { console.log('✅ ColdFusion service started successfully!'); done(); } else { done(new Error(`❌ Failed to start ColdFusion service after ${MAX_RETRIES} retries`)); } }); async function tryServiceStart() { // 查询服务状态,禁用默认日志输出 const services = await sc.query(targetServer, { name: SERVICE_NAME, quiet: true }); if (services.length !== 1) { console.log("Could not find the ColdFusion service."); return false; } const service = services[0]; // 处理启动挂起的情况:杀死进程后返回false触发重试 if (service.state.name === 'START_PENDING') { console.log('Service is stuck in START_PENDING, killing hanging process..'); try { await kill(service.pid, { force: true }); console.log('Hanging process killed successfully.'); return false; } catch (err) { console.log(`Failed to kill process: ${err.message}`); return false; } } // 如果服务已经在运行,直接返回成功 if (service.state.name === 'RUNNING') { console.log('ColdFusion service is already running.'); return true; } // 服务已停止,尝试启动 if (service.state.name === 'STOPPED') { try { await sc.start(targetServer, SERVICE_NAME, { quiet: true }); // 额外查询一次状态,确保服务确实进入运行状态 const updatedService = await sc.query(targetServer, { name: SERVICE_NAME, quiet: true }); const isRunning = updatedService[0].state.name === 'RUNNING'; if (isRunning) { console.log('Service start initiated successfully.'); } else { console.log('Service start initiated but not yet running.'); } return isRunning; } catch (err) { console.log(`Service start failed: ${err.message}`); throw err; // 抛出错误触发重试 } } // 其他状态(比如STOP_PENDING等),暂时返回false console.log(`Service is in ${service.state.name} state, cannot start now.`); return false; }
关键优化点说明
- 禁止重复输出:通过
{ quiet: true }参数关闭sc模块的自动日志,所有日志都由我们手动控制,只打印必要信息。 - async/await循环重试:替代递归Promise,逻辑更直观,避免递归栈溢出,同时错误处理更可控。
- 严谨的状态检查:启动服务后额外查询状态,确保服务真正进入RUNNING状态,而不是仅依赖start方法的成功回调。
- 可维护性:把常量(重试次数、服务名)抽离出来,方便后续修改。
内容的提问来源于stack exchange,提问作者Jmh2013
相关产品推荐
相关产品推荐

