質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Electron

Electronは、HTML5とNode.jsというWebの技術を用いてデスクトップアプリケーションを作成できるクロスプラットフォームな実行環境です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

2344閲覧

javascriptの非同期処理、promiseについて

cisdur

総合スコア46

Electron

Electronは、HTML5とNode.jsというWebの技術を用いてデスクトップアプリケーションを作成できるクロスプラットフォームな実行環境です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

1グッド

1クリップ

投稿2016/06/16 03:18

編集2016/06/16 03:21

###概要
現在、javascriptとelectronを用いて、メモ帳アプリのようなものを作っています。
データベースへのアクセスに伴う非同期処理について、promiseの使い方がよく分からないので質問させてください。
また、classやconstructorの扱い方も、現在の形でいいのかどうか、やや不安を持っています。

###詳細
メモはSQLiteデータベースのpagesテーブルに保存されており、idとcontentを持っています。
メモ帳の画面にはlistとeditorがあり、listの項目(listItem)をクリックするとeditorに該当メモの内容が表示されます。
listItemにはidが格納されていて、クリック→データベースをidで検索→editorにcontentを表示、という流れになります。

私の書いたコードは、次のような感じです。

JavaScript

1class Page { 2 constructor(id) { 3 this.id = id; 4 db.get(`SELECT content FROM pages WHERE id=${id}`, function(err, row) { 5 this.content = row.content; 6 }); 7 } 8 9 show() { 10 $("#editor").html(this.content); 11 } 12} 13 14$(() => { 15 $("#listItem").click((event) => { 16 showPage($(event.currentTarget).attr("data-id")); 17 }); 18}); 19 20function showPage(id) { 21 const page = new Page(id); 22 page.show(); 23}

しかし、このコードの場合、データベースからcontentを取得する前にpage.show()が呼ばれてしまうため、うまく行きません。
最終行のpage.show;setTimeout(() => {page.show;}, 100);などにすると一応動作しますので、他の部分に問題はないと思われます。

promiseオブジェクトなどを用い、データベースへからの値の取得後に、page.show()が呼ばれるようにしたいです。
グローバル変数なpromiseオブジェクトを作ればとりあえずできるような気はしますが、まあ、ダメですよね。
いい方法があれば、ご教示願います。
よろしくお願いいたします。

horse_n_deer👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

複数の非同期処理を実行し、複数のフィールドを初期化することを考慮してPromise.allを使います。
例:

JavaScript

1function Page(id) { 2 this.id = id; 3 var init_promises = []; 4 var self = this; 5 // 配列に非同期処理を追加していく 6 init_promises.push(new Promise(function(res, rej) { 7 setTimeout(function() { 8 self.content = "Hello"; 9 res(); 10 }, 1000); 11 })); 12 init_promises.push(new Promise(function(res, rej) { 13 setTimeout(function() { 14 self.aside = "world!"; 15 res(); 16 }, 2000); 17 })); 18 this.__initialized = Promise.all(init_promises); 19} 20 21Page.prototype.show = function() { 22 var self = this; 23 this.__initialized.then(function() { 24 alert(self.content + " " + self.aside); 25 }); 26} 27 28var page = new Page(1); 29page.show();

全部の非同期初期化処理が終わった後にいろいろできます。

this.__initialized.then(function() { // 初期化が終わった後にやりたい処理 });

質問者様が使っているのはnode-sqlite3でしょうか?
ちらっとしか確認してませんが全部コールバックで受け取るみたいですね。DBからデータを複数行取得する場合でもDatabase#allを使うか、Database#eachでcompleteコールバックを利用すればいけそうですね。

投稿2016/06/16 05:44

guest1213

総合スコア306

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

cisdur

2016/06/16 06:42

なるほど、できました! ありがとうございました。 コードも載せたいので、具体的な私の解決方法は所定の欄に書きますね。 あと、おっしゃる通り、node-sqlite3を使用しています。 Database#allもDatabase#eachも使ったことがありますので、今後は複数行取得の場合で今回のような場面が出てきても問題なさそうです。
guest

0

guest1213様の回答をもとに、解決いたしました。
修正したコードは以下のようになりました。
guest1213様はpromise.allを使った方法を提示してくださいましたが、現状の私のアプリでは複数の非同期処理を平行するわけではないため、1つのpromiseオブジェクトで問題ありませんでした(複数のフィールドからプロパティを取ってくると言っても、今のところ取得元のテーブルは1つなので、SELECT *とでもすればOK)。

JavaScript

1class Page { 2 constructor(id) { 3 this.id = id; 4 this.promise = new Promise((resolve) =>{ 5 db.get(`SELECT content FROM pages WHERE id=${id}`, function(err, row) { 6 this.content = row.content; 7 resolve(); 8 }); 9 }); 10 } 11 12 show() { 13 this.promise.then(() => { 14 $("#editor").html(this.content); 15 }); 16 } 17} 18 19$(() => { 20 $("#listItem").click((event) => { 21 showPage($(event.currentTarget).attr("data-id")); 22 }); 23}); 24 25function showPage(id) { 26 const page = new Page(id); 27 page.show(); 28}

なるほど、promiseってこう使うのか!という感じです。
今まで、thenをfunction showPage(id){}のブロックの中で使わなければどうにもならないと思っていました。
なんというか、「準備ができたらすぐにthen以下を呼ぶ」というようなイメージだったので。
でも、promiseは状態を保持した上で一種の条件分岐をするのだから、確かにこういう書き方でいいのですね。

とても勉強になりました。
回答をくださった皆様、誠にありがとうございました。

投稿2016/06/16 07:12

編集2016/06/16 08:13
cisdur

総合スコア46

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

promiseなんて使わなくてもこれではだめですか?

Javascript

1class Page { 2 constructor(id) { 3 this.id = id; 4 } 5 6 show() { 7 db.get(`SELECT content FROM pages WHERE id=${id}`, function(err, row) { 8 this.content = row.content; 9 $("#editor").html(this.content); 10 }); 11 } 12}

#追記
どうしてもコンストラクタで完了を待ちたいのであれば、お勧めはしませんがこのような方法があります。

Javascript

1constructor(id) { 2 this.id = id; 3 this.loaded = false; 4 db.get(`SELECT content FROM pages WHERE id=${id}`, function(err, row) { 5 this.content = row.content; 6 this.title = row.title; 7 this.loaded = true; 8 }); 9 10 // 非同期処理内でフラグが立つまで無駄な処理を行う 11 var waitCount = 0; 12 while(!this.loaded) { 13 waitCount++; 14 } 15}

Javascriptにはsleep等のメソッドが無いため、ブロッキングしてスレッドを休止させる方法がありません。CPUを無駄に使って待たせることになりますし、全ての環境でちゃんとsleepの代わりになるかどうかわかりません。

投稿2016/06/16 03:22

編集2016/06/16 05:06
masaya_ohashi

総合スコア9206

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

cisdur

2016/06/16 03:31

ご回答、ありがとうございます。 promiseを使うこと自体にはこだわっていませんので、使わない方法でも構いません。 ただ、実際のアプリではcontent以外にもいくつかデータベースに保存されたプロパティがあり、場合によってはshow()の前にそれらのプロパティを使うかもしれませんから、constructorの中で全てのプロパティに値を設定しておきたいのです。 つまり、db.getはconstructorの中で呼びたい、ということです。
masaya_ohashi

2016/06/16 03:45

それはコンストラクタが完了した時点でDBからの抽出が終わっていることを前提とした設計になっていませんか?DBのgetが非同期である以上、そのような設計は問題があるように感じますが…あなたが望む形は以下のような状況ということですか? const page = new Page(id); // インスタンス時点でDBから抽出が終わって、contentの中身が入っている page.show();
cisdur

2016/06/16 04:43

「コンストラクタが完了した時点でDBからの抽出が終わっていることを前提とした設計になって」……いますね。 なるほど、問題がありますか……。 望む状況は、masaya_ohashi様のおっしゃる通りです。 Pageオブジェクトを生成した時点で、「次にどのメソッドにもcontent(を含む、さまざまな)プロパティが渡せる」状況にしておきたいのです。 たとえば、Pageオブジェクトにid、contentの他にtitleというプロパティがあり、こちらもデータベースに格納されているとします。 そして、listItemに2つのボタンを設置し、「ボタンAが押されたらtitleとcontentの内容を表示し、ボタンBが押されたらtitleの文字列でweb検索する」ということを考えます。 すると、masaya_ohashi様の方法では、 class Page { constructor(id) { this.id = id; } 表示() { db.get(`SELECT title, content FROM pages WHERE id=${id}`, function(err, row) { this.title = row.title; this.content = row.content; 表示する処理(this.title, this.content); }); } Web検索() { db.get(`SELECT title FROM pages WHERE id=${id}`, function(err, row) { this.title = row.title; Web検索する処理(this.title) }); } } という感じになると思うのですが、これだとdb.getをメソッドの数だけ書かなければいけませんし、「表示した後にWeb検索をする」という場合には、2回データベースへのアクセスが生じます。 そこで、 class Page { constructor(id) { this.id = id; db.get(`SELECT * FROM pages WHERE id=${id}`, function(err, row) { this.content = row.content; this.title = this.title; }); } 表示() { 表示する処理(this.title, this.content); } Web検索() { Web検索する処理(this.title) } } のようにしたい、ということです。 プロパティがcontent以外にも多数ある点、メソッドも将来増えてくる可能性がある点などが後出しになってしまったことをお詫びいたします。 また、「getは非同期である以上、設計に問題があるかもしれない」という指摘にはハッとさせられました。
cisdur

2016/06/16 06:45

おすすめしない方法は、確かにまずそうですね。 今回は、他の方の回答で、promiseを使った良さそうな方法があったので、そちらを採用することにしました。 繰り返し迅速なお答えをいただき、ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問