better-sqlite3に移行しては?
Easy-to-use synchronous API (better concurrency than an asynchronous API... yes, you read that correctly)
非同期ライブラリじゃなくて同期ライブラリだから使いやすいよって言ってます。
現状のコードを改修する形でやるなら
そもそもの前提知識が不足しているので
非同期処理についてちゃんと学習する必要があります
- JS(Node.js含む)の非同期処理の動作順番
- どうすれば目的の挙動をするコードを書けるのか
- Promiseで置き換える
- Promiseをasync/await構文で置き換える
上記4項目、上から順番に理解していけば問題は解消されますが、
調べもの・学習が捗るように軽く解説していきます。
1. JS(Node.js含む)の非同期処理の動作順番
JSはイベント駆動という話を聞いた事があると思います。
HDDやネットワーク越しの通信など、何時終わるか分からんクソ遅い挙動や、
Webサイトを訪問したユーザーのマウスクリックやスクロール状況によって初めて動作して欲しい挙動
JSの本来の目的はHTML(DOMツリー)を後から編集するためのスクリプト言語なので
JSは「待つ」事が求められました。
そのため「イベント駆動」という考え方を採用しています。
JSにはイベントループという概念が存在します。
イベント置き場に「達成条件」と「動作して欲しいコード片(関数)」をセットにして、
「イベント登録」を行います。
このイベント登録時、JSは「はいはい、イベント登録ね、後で見とくわ」とスルーします。
そして全てのJSコードの実行が終わってから達成しているイベントを巡回して、対になっている関数を実行してまわります。
例として下記のような0秒待つコードを書いてみましょう。
その場で条件達成しているのですが、
イベント登録だけでスルーされて、後のイベントループで抽出されるので後から実行される事が確認できます。
js
1console.log(1);
2setTimeout(
3 () => console.log(2),
4 0
5);
6console.log(3);
7// 1 -> 3 -> 2の順番に表示される
まぁ、ここまでは勉強出来てる内容通りです。
次
2. どうすれば目的の挙動をするコードを書けるのか
一度非同期の世界に入ったら
イベントループの先になるので同期処理には戻れません。
なので、このメソッド自体をコールバック対応関数として作成する必要があります。
その上でやりたいコードを全部関数の中にぶち込む事になります。
こんな感じ
js
1const obj = {
2 getdata : (query, cb) => {
3 db.all(query, [], (err, rows) => {
4 db.close();
5 cb(err, rows); // コールバック関数の第一引数にエラー状況を埋める慣習がある
6 });
7 }
8}
いやいや、結局そこでそうなるんじゃ何の意味もないでしょ。
でしょうね。
一生コールバック関数がネストしていく所謂「コールバック地獄」に陥ります。
これを解決させる為にJS使い達は苦戦を強いられてきました。
こういう数珠つなぎで長い非同期コードを管理する為のベストプラクティスが2015年以降の新しいJSで実装されました
後続のPromiseとasync/await構文です。
3. Promiseで置き換える
Promise - MDN
js
1const obj = {
2 getdata : (query, val = []) => new Promise((resolve, reject) => {
3 db.all(query, val, (err, rows) => {
4 db.close();
5 if (err) {
6 return reject(err);
7 }
8 resolve(rows);
9 });
10 })
11}
昔ながらの非同期処理のコールバックをPromiseで扱う
bluebirdのようなライブラリも存在します。
ですがまぁ、Promiseもイベント登録→イベントループの先で実行するという制約を乗り越えた訳ではないので、
コールバック地獄でネストしまくって100文字インデントした後に処理を記述する事から逃げられた代わりに
.then
メソッドを叩きまくるというコールバック地獄のもう一つの問題は解消されていません。
なのでasync/await構文まできっちり押さえないと
やりたい事にはたどり着けません。
頑張ってください。
4. Promiseをasync/await構文で置き換える
非同期関数(async) - MDN
await演算子 - MDN
これらは内部処理でPromiseを上手く扱って、
同期処理っぽいコードが書ける糖衣構文です。
早速上記のPromise化したgetdataを扱ってみましょう。
js
1const obj = {
2 getdata : (query, val = []) => new Promise((resolve, reject) => {
3 db.all(query, val, (err, rows) => {
4 db.close();
5 if (err) {
6 return reject(err);
7 }
8 resolve(rows);
9 });
10 })
11}
12
13// awaitを使う為にはasync関数を宣言する必要がある
14const main = async () => {
15 // awaitにはPromise.thenメソッドを叩いて第一引数を取り出す機能が存在する
16 const rows = await obj.getdata("SELECT * FROM users");
17 console.log(rows);
18
19 // awaitはfor文の中でも使える
20 for (const id of [1, 2, 3]) {
21 const result = await obj.getdata("SELECT * FROM users WHERE id = ?", [id]);
22 console.log(result);
23 }
24
25 // catchメソッドに該当するコードが書きたい場合、try-catchでくくる
26 try {
27 const rows = await obj.getdata("SELECT * FROM foo");
28 console.log(rows);
29 } catch (e) {
30 console.error(e);
31 }
32
33 // async関数内でreturnした場合、返り値は必ずPromiseになる事に注意
34 // 下記の123という値を取り出す時はthenメソッドかawait構文を使う必要がある
35 return 123;
36}
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/04/09 23:21