前提
(問題自体に不備があったので質問の意図自体を変更します)
Electron (node.js)初挑戦です。sqlite3を使いたいと思い、npmを使ってインストールしました。
サンプルとしてそのsqlite3を使ってデータベースからselectをした結果を取得しようとしています。
実現したいこと
- 前提にある問題(非同期であるため処理が前後していて、値がおかしいこと)の解決
→ どのようにして安全にデータをmain.jsからrenderer.jsを渡すか
発生している問題・エラーメッセージ
main.js内のipcMain.handleメソッド内でsqlite3を使う処理をして、renderer.jsでinvokeして起動します。
ただ、(sqliteからselectするために使う)sqlite3.Database#allメソッド等は非同期らしく、allメソッドの次の処理と前後が逆になることがあり、renderer.jsに戻り値として返されるデータが常に空か{}
のような中身のないデータになっています。
「Electron node.js sqlite3」と調べてもよくて、Electronの古いバージョンでのサンプル(renderer.jsに直接requireするとか)か、単にdb.all("select * from users", (error, rows) =>{ console.log( rows ); });
のように、「ラムダ式の中でそのまま出力するだけ」となっています。
データを取り出すだけならいいのですが、非同期処理なので値が入ったり入らなかったりするのでそこで頭を悩ませています。
該当のソースコード
JavaScript
1// main.js 2// アプリケーション作成用のモジュールを読み込み 3const { app, BrowserWindow, ipcMain } = require("electron"); 4const path = require("path"); 5 6const sqlite3 = require( "sqlite3" ); 7const db = new sqlite3.Database('test.db'); 8 9// メインウィンドウ 10let mainWindow; 11 12const createWindow = () => { 13 // メインウィンドウを作成します 14 mainWindow = new BrowserWindow({ 15 width: 800, 16 height: 600, 17 webPreferences: { 18 preload: path.join(__dirname, "preload.js"), 19 } 20 }); 21 22 // メインウィンドウに表示するURLを指定します 23 // (今回はmain.jsと同じディレクトリのindex.html) 24 mainWindow.loadFile("index.html"); 25 26 // デベロッパーツールの起動 27 mainWindow.webContents.openDevTools(); 28 29 // メインウィンドウが閉じられたときの処理 30 mainWindow.on("closed", () => { 31 mainWindow = null; 32 // ここで閉じておく 33 db.close(); 34 }); 35 36 ipcMain.handle( "create_table", (_evnet, _arg) => { 37 db.serialize( function(){ 38 // テーブル作成 39 db.run( "create table users (name text, email text, age int)" ); 40 }); 41 return true; 42 }); 43 44 ipcMain.handle( "insert_record", (_evnet, _arg) => { 45 db.serialize( function(){ 46 // INSERT INTO テーブル名 VALUES (値1, 値2,...); 47 db.run( "insert into users values ('太郎', '****', 19)" ); 48 }); 49 return true; 50 }); 51 52 ipcMain.handle( "insert_recordEx", (_evnet, _arg) => { 53 db.serialize( function(){ 54 var stmt = db.prepare("insert into users('name', 'email', 'age') values(?, ?, ?)"); 55 stmt.run( '次郎", '****', 20 ); 56 stmt.finalize(); 57 }); 58 return true; 59 }); 60 61 ipcMain.handle( "select_record", (_evnet, _arg) => { 62 db.serialize( function(){ 63 /* 64 // 一個ずつ取り出す場合 65 db.each("SELECT * FROM users", (err, row) => { 66 console.log( row.name ); 67 }); 68 */ 69 70 /* 71 // 一括で取り出す場合 72 db.all("select * from users", (error, rows) =>{ 73 rows.forEach(row => console.log(row.name)); 74 }); 75 */ 76 77 /* 78 db.all("select * from users", (error, rows) =>{ 79 console.log( rows ); 80 }); 81 */ 82 83 /* 84 // 最初にヒットしたものを取り出す 85 db.get("select * from users", (error, row) =>{ 86 console.log( row ); 87 }); 88 */ 89 90 // HERE! 91 let txt = ""; // ① 92 db.all("select * from users", (error, rows) =>{ 93 rows.forEach(row => { txt += "hello"; }); // ② 94 }); 95 console.log( "select = " + txt ); // ③ 96 }); 97 return false; 98 }); 99}; 100 101// 初期化が完了した時の処理 102app.whenReady().then(() => { 103 createWindow(); 104 105 // アプリケーションがアクティブになった時の処理(Macだと、Dockがクリックされた時) 106 app.on("activate", () => { 107 // メインウィンドウが消えている場合は再度メインウィンドウを作成する 108 if (BrowserWindow.getAllWindows().length === 0) { 109 createWindow(); 110 } 111 }); 112}); 113 114// 全てのウィンドウが閉じたときの処理 115app.on("window-all-closed", () => { 116 // macOSのとき以外はアプリケーションを終了させます 117 if (process.platform !== "darwin") { 118 app.quit(); 119 } 120});
JavaScript
1// renderer.js 2const { contextBridge, ipcRenderer } = require('electron') 3 4contextBridge.exposeInMainWorld( 5 'electronAPI', { 6 setTitle: (title) => ipcRenderer.send('set-title', title), 7 createTable : () => { 8 ipcRenderer.invoke( "create_table", "ok" ).then( (result) => { 9 console.log( result ); 10 }); 11 }, 12 insertRecord : () => { 13 ipcRenderer.invoke( "insert_record", "ok" ).then( (result) => { 14 console.log( result ); 15 }); 16 }, 17 insertRecordEx : () => { 18 ipcRenderer.invoke( "insert_recordEx", "ok" ).then( (result) => { 19 console.log( result ); 20 }); 21 }, 22 selectRecord : () => { 23 ipcRenderer.invoke( "select_record", "ok" ).then( (result) => { 24 console.log( result ); 25 }); 26 } 27})
html
1<!-- index.html --> 2<!DOCTYPE html> 3<html> 4<head> 5 <meta charset="UTF-8" /> 6 <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> 7 <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'" /> 8 <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'" /> 9 <title>Hello from Electron renderer!</title> 10</head> 11<body> 12 13 <form> 14 <button type="button" id="createTableButton">create table</button> 15 <button type="button" id="insertRecordButton1">insert record 1</button> 16 <button type="button" id="insertRecordButton2">insert record 2</button> 17 <button type="button" id="selectRecordButton">select</button> 18 </form> 19 20 <script src="./lib1.js"></script> 21</body> 22</html>
JavaScript
1// lib1.js 2 3document.getElementById("createTableButton").addEventListener('click', () => { 4 window.electronAPI.createTable(); 5}); 6 7document.getElementById("insertRecordButton1").addEventListener('click', () => { 8 window.electronAPI.insertRecord(); 9}); 10 11document.getElementById("insertRecordButton2").addEventListener('click', () => { 12 window.electronAPI.insertRecordEx(); 13}); 14 15document.getElementById("selectRecordButton").addEventListener('click', () => { 16 window.electronAPI.selectRecord(); 17});
試したこと
[やったこと1]
(最初はラムダ式の外にあるlet txtに入っていないのかと思い、質問して)txtの値を確認した
JavaScript
1db.all("select * from users", (error, rows) =>{ 2 console.log( "all" ); 3 rows.forEach(row => { txt += "hello"; }); 4 console.log( txt ); 5}); 6console.log( "out: " + txt );
[結果1]
renderer.js内のselectRecordメソッドでのconsole.log( result );ではfalseが表示され、コンソールには
out: all hellohello
と空になっていた。(それとラムダ式の関数が後から動いているらしい。つまり順番が違う。)
[やったこと2]
やったこと1のラムダ式を通常の関数にして渡す。
[結果2]
結果1と変わらず。
[やったこと3]
やったこと1のラムダ式の引数に _txtを追加。
db.all("select * from users", (error, rows, _txt) =>{ console.log( "all" ); rows.forEach(row => { _txt += "hello"; }); });
[結果3]
結果1,2と同じ。
[やったこと4]
ラムダ式内のtxtからするとipcMain.handle内のtxtは別の変数(グローバル変数のような感じ)と解釈されたのだと思い、main.jsの先頭に"use strict";
を付けてみた。
[結果4]
変わらず。
[やったこと5]
公式ドキュメントからall系の一括読み込みメソッドを調べた。
[結果5]
探した範囲では存在しなかった。(コールバック関数を受け取って非同期的に行うものしかなかった。)
[やったこと6]
(非同期処理であり、コールバック関数としてラムダ式が渡されていることから)通常の関数として切り出して、renderer.js側でコールバック関数として定義し、ipcRenderer.invokeの引数として渡して処理させる。
// renderer.jsから抜粋 function callback( err, rows ){ console.log( rows ); // 本来はここで何らかの処理をする } ... selectRecord : () => { ipcRenderer.invoke( "select_record", callback ).then( (result) => { console.log( result ); }); }
// main.jsから抜粋 ipcMain.handle( "select_record", (_evnet, f) => { db.serialize( function(){ let txt = ""; db.all("select * from users", (error, rows) =>{ f( error, rows ); }); //console.log( "select = " + txt ); }); return false; });
[結果6]
VM4 sandbox_bundle:29 Uncaught (in promise) Error: An object could not be cloned.
というエラーメッセージがディベロッパーツール上に表示される。
→ 調べるとメインプロセスとレンダラープロセスは分断されているために起こるエラー。ただし仕様上のものなので対処できないらしい?
[やったこと7]
念のためdb.serializeに渡している無名関数の引数としてfを指定した。
[結果7]
結果6と同様。
後思いつく方法としては『メインプロセスでDB操作を行いながら、直接DOM操作も行う』という方法ですが、できればメインプロセスはウィンドウの処理をして、DOM操作を行うのはレンダラープロセスに任せたいのです。
補足情報(FW/ツールのバージョンなど)
言語: JavaScript
node.js : node v16.17.0
Electron: Electron v21.1.1

回答2件
あなたの回答
tips
プレビュー