db.serialize() から逃げた

[ Node.js v10.15.3 / sqlite3@4.0.9 ]

たとえば id が 1, 2, 3, 4, 5 のレコードを1行ずつ取得して、content カラムの末尾に『!』を付与してUPDATEするというコードを、db.serialize() 使って書いてみます。

※動きません
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('/path/to/mydatabase');

db.serialize(() => {
	const ids = [1,2,3,4,5];
	
	for (let id of ids) {
		// 1行取得
		db.get(`SELECT * FROM mytable WHERE id = ?`, [id], (err, row) => {
			if (err) return console.log('※SELECTでエラー', err);
			
			// 更新
			const newContent = row.content + '!';
			db.run(`UPDATE mytable SET content = ? WHERE id = ?`, [newContent, id], (err) => {
				if (err) return console.log('※UPDATEでエラー', err);
			});
		});
	}
	
	// データベースを閉じる
	db.close(err => {
		if (err) return console.log('※closeでエラー', err);
	});
});

上記のコードは動きません。db.run() で以下のエラーが出てしまいます。

実行結果
※UPDATEでエラー { [Error: SQLITE_MISUSE: Database handle is closed] errno: 21, code: 'SQLITE_MISUSE' }
※UPDATEでエラー { [Error: SQLITE_MISUSE: Database handle is closed] errno: 21, code: 'SQLITE_MISUSE' }
※UPDATEでエラー { [Error: SQLITE_MISUSE: Database handle is closed] errno: 21, code: 'SQLITE_MISUSE' }
※UPDATEでエラー { [Error: SQLITE_MISUSE: Database handle is closed] errno: 21, code: 'SQLITE_MISUSE' }
※UPDATEでエラー { [Error: SQLITE_MISUSE: Database handle is closed] errno: 21, code: 'SQLITE_MISUSE' }

そうでしょうね。db.run()db.serialize() の直下で実行してないので、それをやる頃にはすでに db.close() が流れてしまってるからです。

上記のコードは db.close() を消せば一応動きます。プログラム的にデータベースを閉じるタイミングがわからないので、終了までしばらく待たされますが、動くことは動きます。でもそれでいいのかよっていう。

そもそも db.serialize() が難しいです。というか、自分ひとりでちょっと使いたいだけだったりすると、結果をコールバック関数で受け取るのがしんどいです。

どうするの

自分ひとりでちょっと使うだけなら、db.serialize() 使わないで、Promise化して await で呼んでしまいます。こっちの方が、本当に何も考えなくていいです。

Promise化してawaitで呼ぶ
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('/path/to/mydatabase');

const ids = [1,2,3,4,5];

// awaitはasyncの中じゃないと使えない
(async () => {
	for (let id of ids) {
		// 1行取得
		const row = await get(`SELECT * FROM mytable WHERE id = ?`, [id]);
		
		// 更新
		const newContent = row.content + '!';
		await run(`UPDATE mytable SET content = ? WHERE id = ?`, [newContent, id]);
	}
	
	// データベースを閉じる
	db.close(err => {
		if (err) return console.log('※closeでエラー', err);
	});
})();

function get(sql, params) {
	return new Promise((resolve, reject) => {
		db.get(sql, params, (err, row) => {
			if (err) reject(err);
			resolve(row);
		});
	});
}

function run(sql, params) {
	return new Promise((resolve, reject) => {
		db.run(sql, params, (err) => {
			if (err) reject(err);
			resolve();
		});
	});
}

このサンプル程度の処理だと無駄にコードが長くなるだけですが、もっと複雑なことをやり始めたら、ただただ書いた順番通りに動いてくれるのは楽です。