Если запустить на выполнение длительную операцию, то текущий процесс будет блокирован. В результате объект приложения или окно перестанут отвечать на действия пользователя и системы. Чтобы этого избежать, следует запустить длительную операцию в отдельном процессе. Запустить отдельный процесс позволяет функция fork()
из модуля child_process
. Формат функции:
const { fork } = require('child_process');
<ChildProcess> = fork(<Модуль>[, <Аргументы>][, <Опции>])
В первом параметре указывается путь к запускаемому в отдельном процессе модулю. Во втором параметре можно указать массив с передаваемыми аргументами. Третий параметр позволяет указать объект с опциями. Список опций см. в документации. Функция возвращает объект ChildProcess
.
Модуль будет запущен в отдельном дочернем процессе, который изолирован от других процессов. Однако существует возможность обмена сообщениями между родительским и дочерним процессами. Чтобы отправить сообщение дочернему процессу, следует вызвать метод send()
через объект ChildProcess
:
let child = fork('test2.js');
child.send( {command: 'start', str: 'string'} );
Для отправки сообщения из дочернего процесса используется метод send()
из модуля process
:
process.send(s);
Передаваемые между процессами данные должны поддерживать сериализацию.
Чтобы получить сообщение в дочернем процессе, следует назначить обработчик события message
с помощью метода on()
из модуля process
:
process.on('message', (obj) => {
// ...
});
Внутри обработчика через параметр доступно значение, переданное методом send()
.
Чтобы получить сообщение, отправленное дочерним процессом, внутри родительского процесса, следует назначить обработчик события message
с помощью метода on()
через объект ChildProcess
:
child.on('message', (s) => {
// ...
});
Внутри обработчика через параметр доступно значение, переданное методом send()
.
Объект ChildProcess
поддерживает следующие события:
message
— из дочернего процесса отправлено сообщение с помощью метода send()
из модуля process
. Внутри обработчика через первый параметр доступны переданные данные;error
— генерируется при возникновении ошибки. Внутри обработчика через параметр доступен объект ошибки:child.on('error', (e) => {
console.log(e);
});
disconnect
— вызван метод disconnect()
. Обмен сообщениями становится невозможным:child.on('disconnect', () => {
console.log('disconnect');
});
close
— при закрытии потоков дочернего процесса. Внутри обработчика через первый параметр доступен код завершения, если процесс завершился сам, или значение null
. Через второй параметр доступен сигнал, по которому дочерний процесс был завершен, или значение null
:child.on('close', (code, signal) => {
console.log('close', code, signal);
});
exit
— после завершения работы дочернего процесса. Внутри обработчика через первый параметр доступен код завершения, если процесс завершился сам, или значение null
. Через второй параметр доступен сигнал, по которому дочерний процесс был завершен, или значение null
:child.on('exit', (code, signal) => {
console.log('exit', code, signal);
});
Принудительно завершить работу дочернего процесса из родительского процесса позволяет метод kill([<Сигнал>])
. Если параметр не указан, то генерируется сигнал SIGTERM
. Метод возвращает значение true
, если операция выполнена успешно, и false
— а противном случае:
child.kill();
Давайте рассмотрим пример. При нажатии кнопки получим содержимое текстового поля и выполним перевод букв в верхний регистр (просто для примера). Операцию запустим в отдельном процессе. Содержимое файла main.js
приведено в листинге 11.2, файла index.htm
— в листинге 11.3, файла test.js
— в листинге 11.4, а файла test2.js
— в листинге 11.5.
Листинг 11.2. Содержимое файла C:\book\e1\main.js
const { app, BrowserWindow } = require('electron');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
win.loadFile('index.html');
win.webContents.openDevTools();
}
app.whenReady().then( () => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
} );
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
Листинг 11.3. Содержимое файла C:\book\e1\index.html
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'">
<title>Выполнение операции в отдельном процессе</title>
</head>
<body>
<h1>Выполнение операции в отдельном процессе</h1>
<input type="text" id="txt1">
<button type="button" id="btn1">Запустить процесс</button><br>
<div id="result"></div>
<script src="test.js"></script>
</body>
</html>
Листинг 11.4. Содержимое файла C:\book\e1\test.js
const { fork } = require('child_process');
document.getElementById('btn1').addEventListener('click', () => {
let txt1 = document.getElementById('txt1');
let result = document.getElementById('result');
if (txt1.value === '') {
result.innerHTML = 'Не заполнено поле';
return;
}
let child = fork('test2.js');
child.on('message', (s) => {
result.innerHTML = s;
child.send( {command: 'exit'} );
// child.kill();
});
child.on('exit', (code, signal) => {
console.log('exit', code, signal);
});
child.on('error', (e) => {
console.log(e);
});
child.on('close', (code, signal) => {
console.log('close', code, signal);
});
child.on('disconnect', () => {
console.log('disconnect');
});
child.send( {command: 'start', str: txt1.value} );
});
Листинг 11.5. Содержимое файла C:\book\e1\test2.js
function start(str) {
return str.toUpperCase();
}
process.on('message', (obj) => {
if (obj.command === 'start') {
let s = start(obj.str);
process.send(s);
}
else if (obj.command === 'exit') {
process.exit();
}
});