остановить запросы нескольких сервисных контроллеров внутри NodeJs, обещая while цикл

Я пытаюсь запустить службу Windows из сценария узла. Эта служба имеет плохую привычку зависать и иногда требует повторного запуска для успешного запуска. У меня есть обещание настроить цикл while (пожалуйста, предложите лучший способ). Проблема, с которой я столкнулся, заключается в том, что с каждым циклом вывод sc.pollInterval записывает повторяющиеся результаты в консоль. Ниже приведен пример дублированного контента, который я вижу в консоли, это после второй итерации в цикле, я бы хотел, чтобы он отображал этот контент только один раз.

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              :

Вот код, который у меня есть. В принципе, я попробую запустить сервис 3 раза. Если нет, то выкидываю и ошибаюсь. Следует отметить, что когда я пытаюсь запустить службу, но она застряла в состоянии «Start_pending», я завершаю процесс, а затем пытаюсь запустить его снова.

var retryCount = 0;

// Start the colfusion service
gulp.task('start-coldfusion-service', function(done) {
    var serviceStarted = false;
    console.log("Starting coldfusion service..");
    // This says we're going to ask where it's at every 30 seconds until it's in the desired state.
    sc.pollInterval(30);
    sc.timeout(60);
    retryCount = 0;

    tryServiceStart().then(function(result) {
          // process final result here
        done();
    }).catch(function(err) {
        // process error here
    });
});


function tryServiceStart() {
    return startService().then(function(serviceStarted) {
        if (serviceStarted == false) {
            console.log("Retry Count: " + retryCount);
            // Try again..
            return tryServiceStart();
        } else {
             return result;
        }
    });
}

function startService() {
    return new Promise(function(resolve, reject) {
        var started = true;
        // Make sure the coldfusion service exists on the target server
        sc.query(targetServer, { name: 'ColdFusion 10 Application Server'}).done(function(services) {
            // if the service exists and it is currentl stopped, then we're going to start it.
            if (services.length == 1) {
                var pid = services[0].pid;
                if (services[0].state.name == 'STOPPED') {
                    sc.start(targetServer, 'ColdFusion 10 Application Server')
                        .catch(function(error) {
                            started = false;
                            console.log("Problem starting Coldfusion service! error message: " + error.message);
                            console.log("retrying...");
                            retryCount++;
                            if (parseInt(retryCount) > 2) {
                                throw Error(error.message);
                            }
                       })
                       .done(function(displayName) {
                            if (started) {
                                console.log('Coldfusion service started successfully!');
                            }
                            resolve(started);
                       });
                } else if (services[0].state.name == 'START_PENDING') {
                    kill(pid, {force: true}).catch(function (err) {
                        console.log('Problem killing process..');
                    }).then(function() {
                        console.log('Killed hanging process..');
                        resolve(false);
                    });
                }
            } else {
                console.log("Could not find the service in a stopped state.");
                resolve(false);
            }
        });
   });
}

person Jmh2013    schedule 17.01.2018    source источник
comment
Пакет service-control-manager утверждает, что все команды возвращают обещание, однако документация (которой правильно придерживается ваш код) сообщает нам, что у этих так называемых обещаний есть методы .done() и .catch(). Далеко не ясно, обладает ли .done() всей мощью правильного .then() или .done() - всего лишь отстой, как .done() jQuery. Примеры предполагают последнее, и я не нашел в Интернете ничего, что говорило бы мне об обратном.   -  person Roamer-1888    schedule 18.01.2018
comment
Спасибо за информацию @ Roamer-1888. Я считаю, что вы правы в том, что он похож на метод .done() в JQuery, поскольку все, что находится внутри, выполняется каждый раз, независимо от того, отклонено ли обещание или выполнено. В любом случае, я нашел решение, которое мне нравится использовать пакет Promise-retry. Я скоро отправлю новый код в качестве ответа.   -  person Jmh2013    schedule 18.01.2018
comment
Jmh2013, сегодня я поигрался с некоторыми идеями. Я отправлю их в качестве ответа, когда вернусь на свой рабочий стол.   -  person Roamer-1888    schedule 18.01.2018


Ответы (2)


Не совсем уверен, почему вы получаете повторяющиеся результаты в консоли, однако ниже приведены некоторые идеи о том, как можно было бы лучше написать код, в основном путем обещания на самом низком уровне.

Придерживаясь оригинальной концепции, я остановился на этом ...

Обещать команды sc

  • Команды sc возвращают что-то похожее на обещание, но с методом .done (), который, по всей вероятности, не обладает всей мощью подлинного .then ()
  • обещайте каждую команду как .xxxAsync()
  • приняв .done каждой команды как .then, Promise.resolve () сможет ассимилировать обещание, возвращаемое командой.
;(function() {
    commands.forEach(command => {
        sc[command].then = sc[command].done;
        sc[command + 'Async'] = function() {
            return Promise.resolve(sc[command](...arguments)); 
        };
    }).
}(['start', 'query'])); // add other commands as required

gulp.task ()

  • Цепочка обещаний следует своему пути успеха, если служба была открыта, в противном случае - путь ошибки
  • нет необходимости проверять result для обнаружения условий ошибки на пути успеха.
gulp.task('start-coldfusion-service', function(done) {
    console.log('Starting coldfusion service..');
    // This says we're going to ask where it's at every 30 seconds until it's in the desired state.
    sc.pollInterval(30);
    sc.timeout(60);
    tryServiceStart(2) // tryServiceStart(maxRetries)
    .then(done) // success! The service was started.
    .catch(function(err) {
        // the only error to end up here should be 'Maximum tries reached'.
        console.err(err);
        // process error here if necessary
    });
});

tryServiceStart ()

  • организовать повторные попытки здесь
function tryServiceStart(maxRetries) {
    return startService()
    // .then(() => {}) // success! No action required here, just stay on the success path.
    .catch((error) => {
        // all throws from startService() end up here
        console.error(error); // log intermediate/final error
        if(--maxRetries > 0) {
            return tryServiceStart();
        } else {
            throw new Error('Maximum tries reached');
        }
    });
}

startService ()

  • сформировать полноценную цепочку обещаний, вызвав обещанные версии sc.query() и sc.start()
  • console.log() продули в пользу заброса.
  • возникшие ошибки будут перехвачены и записаны в tryServiceStart()
function startService() {
    // Make sure the coldfusion service exists on the target server
    return sc.queryAsync(targetServer, { name: 'ColdFusion 10 Application Server'})
    .then((services) => {
        // if the service exists and it is currently stopped, then start it.
        if (services.length == 1) {
            switch(services[0].state.name) {
                case 'STOPPED':
                    return sc.startAsync(targetServer, 'ColdFusion 10 Application Server')
                    .catch((error) => {
                        throw new Error("Problem starting Coldfusion service! error message: " + error.message);
                    });
                break;
                case 'START_PENDING':
                    return kill(services[0].pid, { 'force': true })
                    .then(() => {
                        throw new Error('Killed hanging process..'); // successful kill but still an error as far as startService() is concerned.
                    })
                    .catch((err) => {
                        throw new Error('Problem killing process..');
                    });
                break;
                default:
                    throw new Error("Service not in a stopped state.");
            }
        } else {
            throw new Error('Could not find the service.');
        }
    });
}

Проверяется только на наличие синтаксических ошибок, поэтому может потребоваться отладка.

Предлагается FWIW. Не стесняйтесь принимать / совершать набеги по мере необходимости.

person Roamer-1888    schedule 18.01.2018
comment
Извините за поздний ответ, я был в коротком отпуске. Мне очень нравится то, что ты здесь сделал. Однако я все еще получал дублирующиеся результаты в консоли. Пакет promise-retry исправил это для меня. Поэтому я удалил функцию tryServiceStart() и использую пакет Promise-retry для организации повторных попыток прямо в задаче start-coldfusion-service. Я адаптировал свой код, чтобы он точно соответствовал тому, что у вас здесь. Мне особенно нравится, как ошибки будут всплывать, как должны сейчас. - person Jmh2013; 22.01.2018
comment
Я не уверен, что вызвало повторяющиеся результаты или почему Promise-retry должен их исправить. Должна быть какая-то тонкая разница между поведением моего кода и Promise-retry, но, черт возьми, я не могу представить, что это может быть. В любом случае, важно то, что у вас есть решение. Удачи с этим. - person Roamer-1888; 22.01.2018

Я нашел еще один пакет npm под названием promise-retry, который, похоже, решил мою проблему. В то же время я считаю, что это сделало мой код более понятным в отношении того, что он делает.

gulp.task('start-coldfusion-service', function(done) {
    var serviceStarted = false;
    console.log("Starting coldfusion service..");
    // Since starting a service on another server isn't exactly fast, we have to poll the status of it.
    // This says we're going to ask where it's at every 30 seconds until it's in the desired state.
    sc.pollInterval(30);
    sc.timeout(60);     

    promiseRetry({retries: 3}, function (retry, number) {
        console.log('attempt number', number);

        return startService()
        .catch(function (err) {
            console.log(err);
            if (err.code === 'ETIMEDOUT') {
                retry(err);
            } else if (err === 'killedProcess') {
                retry(err);
            }

            throw Error(err);
        });
    })
    .then(function (value) {
        done();
    }, function (err) {
        console.log("Unable to start the service after 3 tries!");
        process.exit();
    });

});

function startService() {
    var errorMsg = "";
    return new Promise(function(resolve, reject) {
        var started = true;
        // Make sure the coldfusion service exists on the target server
        sc.query(targetServer, { name: 'ColdFusion 10 Application Server'}).done(function(services) {
            // if the service exists and it is currentl stopped, then we're going to start it.
            if (services.length == 1) {
                var pid = services[0].pid;
                if (services[0].state.name == 'STOPPED') {
                    sc.start(targetServer, 'ColdFusion 10 Application Server')
                        .catch(function(error) {
                            started = false;
                            errorMsg = error;
                            console.log("Problem starting Coldfusion service! error message: " + error.message);
                            console.log("retrying...");
                       })
                       .done(function(displayName) {
                            if (started) {
                                console.log('Coldfusion service started successfully!');
                                resolve(started);
                            } else {
                                reject(errorMsg);
                            }
                       });
                } else if (services[0].state.name == 'START_PENDING') {
                    kill(pid, {force: true}).catch(function (err) {
                        console.log('Problem killing process..');
                    }).then(function() {
                        console.log('Killed hanging process..');
                        reject("killedProcess");
                    });
                } else {
                    // Must already be started..
                    resolve(true);
                }
            } else {
                console.log("Could not find the service in a stopped state.");
                resolve(false);
            }
        });

   });

}
person Jmh2013    schedule 18.01.2018
comment
Будьте осторожны с kill(), который возвращает (мы предполагаем) обещание. Если это так, то с kill(...).catch(...).then(...);, если обратный вызов catch не выбрасывает / повторяет показ, выполнение перейдет к обратному вызову then. В моем решении вы увидите, что я поменял порядок чтения kill(...).then(...).catch(...);. И даже при таком реверсировании убедитесь, что обратный вызов catch вызывает reject(); регистрации недостаточно. - person Roamer-1888; 22.01.2018
comment
Кроме того, для единообразия всегда отклоняйте (или бросайте) правильный Error, а не String. Таким образом, все последующие уловы всегда могут быть записаны для получения Error независимо от того, как / где он возник. - person Roamer-1888; 22.01.2018
comment
Спасибо за советы. Я обновил свой код, чтобы он напоминал ваш ответ. Так что, я считаю, сейчас об этом позаботились. Я впервые использую Node и работаю с обещаниями, поэтому большое спасибо за вашу помощь! Эта часть моего сценария теперь отлично работает! - person Jmh2013; 22.01.2018