Учебник по Electron.js

Выполнение операции в отдельном процессе

Если запустить на выполнение длительную операцию, то текущий процесс будет блокирован. В результате объект приложения или окно перестанут отвечать на действия пользователя и системы. Чтобы этого избежать, следует запустить длительную операцию в отдельном процессе. Запустить отдельный процесс позволяет функция 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 поддерживает следующие события:

child.on('error', (e) => {
   console.log(e);
});
child.on('disconnect', () => {
   console.log('disconnect');
});
child.on('close', (code, signal) => {
   console.log('close', code, signal);
});
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();
   }
});