スクウェア・エニックスノベルス『スタンプ・デッド』1~5巻&コミック版(完結)、『太陽で台風』1・2巻(完結)発売中!
ガンガンノベルズ『魔法少女アーヤ☆アミー』発売中!
徳間デュアル文庫『魔王さんちの勇者さま』1~4巻(完結)発売中!
徳間文庫『欠陥妖怪住宅』、『パラレル家族計画』発売中!
ぽにきゃんBOOKSライトノベルシリーズ『ブチ切れ勇者の世界征服』1~2巻発売中!

2017年4月、第11回HJ文庫大賞にて『銀賞』をいただきました!
2017年5月、ジャンプ小説新人賞’16 Winterにて 小説フリー部門『銀賞』受賞をいただきました!
2017年9月、第30回ファンタジア大賞にて 『金賞』をいただきました!

第11回HJ文庫大賞"銀賞"受賞作『カンスト勇者の超魔教導 ~将来有望な魔王と姫を弟子にしてみた~』
1・2巻、HJ文庫より発売中!
第30回ファンタジア大賞 "金賞"受賞作『お助けキャラに彼女がいるわけないじゃないですか』
3巻、8/18(金)ファンタジア文庫より発売です!

2018-07

«  | ホーム |  »

.thenを用いた非同期処理待ちの世界一シンプル(かもしれない)例

どうも、はむばねです。
昨日もチラッと書きましたが、昨日はJavaScriptで非同期な処理の結果を待ってから処理する方法についてほぼ1日悩んでました。
いやね、jQueryのDeferredを利用すればできそうっていうのは早期段階でわかったし、解説してくれてるところも山ほどあるんですけどね。
どいつもこいつも長ぇよ。
説明も! コードも!
いや、わかってる。
ちゃんと説明しようとすると、色々と書かないといけないことも。
恐らく、本来はそんなに難しいことを書いているわけではないであろうことも。
だがしかし、とにかく私はただ「非同期処理の結果を待って処理を行う」ことがしたいだけなんだ。
そしてこちとらJavaScript素人で、なんだったら括弧が増えただけで結構混乱するんだ。
というわけで、本記事ではできるだけシンプルに、「非同期処理の結果を待って処理を行う」ことだけに絞って説明したいと思います。

本記事のターゲットは、主に……。
・JavaScriptのことなんてよくわからねぇが、とにかく非同期処理のせいでデータ連携が上手くいかねぇ!
・仕様とか実装とか、細けぇことはいいんだよ! 関係するところだけ教えろくだしあ!
・ていうかC言語でいうとどういうことなんだってばよ!
という方向けです。
ターゲット層狭いな!
まぁ基本的に私が情報系の記事を書く時は、過去(調べてた時)の自分がこういう記事見つけてたら嬉しかっただろうな、というのを目標に書いてますからね。
半分備忘録も兼ねてるので、まぁ参考になる人がもしいれば嬉しいね、程度。
……いかん、シンプルにって言ってる割に前置きからして長くなってきましたね。
というわけで、実に4年ぶりのテーマ「情報系」いくよー\(^o^)/

※思ったよか説明が長くなってしまったので、ガチで実際のコードだけ見たい人は一番下の項目まで飛ばしてください。



【前提】
JavaScriptはシングルスレッドで動s細けぇことはいいんだよ!
とにかく、JavaScriptは必ずしも書いた順番通りに動くわけではない!
以上だ!


【サンプル】
じゃあ、例えばどういう時に順番通り動かないのか?
話をシンプルにするため、実際にはクソの役にも立たないサンプルを用意する!

---------------------------------
var ret = sample("after");
document.write(ret);

function sample(str) {
 var tmp = "before";
 setTimeout(function() {
  tmp = str;
 }, 200);
 return tmp;
}
---------------------------------

関数sampleは、0.2秒(200ms)待った後にtmp(初期値:"before")に引数strを代入し、tmpをreturnするだけのクソのような処理を行う!
そのsampleに対して、"after"という文字列をぶち込んで返ってきた値を表示するというクソのようなプログラムだ!
C言語ライクに考えるならば、sample内で0.2秒待った後に"after"が代入されたtmpをreturnし、無事"after"と表示されることを期待するであろう!
しかし、実際にこのプログラムを実行した結果表示されるのは"before"である!
関数内の処理が全て完了する前に表示の処理が実行された(というか変数がreturnされた)ということであろう!


【どうすればいいのか】
jQueryのDeferredオブジェクトを利用するのは、先に述べた通りである!
ではDeferredオブジェクトとは何なのか?
細かい話は省く!
今回のケースで利用するだけならば、「(非同期処理が)終わったかどうかのステータスを管理するオブジェクト」と思っておけばよろしい!
で、実際その「終わったかどうかのステータス」を保存するのが、Deferredが内包するPromiseオブジェクトだ!
Promiseオブジェクトは、作成された時点ではpending(処理中の状態)のステータスを持っている!
これを、Deferredオブジェクトを使ってresolved(成功)かrejected(失敗) かに書き換えることによって、該当処理が終わったかどうかを他の処理に通知(?)できるようにするわけだ!
先の例でいうと、sample内にDeferredオブジェクトを定義して、Deferredオブジェクトから「終わったよ」と通知が来ない限り次の処理に進まないよう呼び出し元を書き換えればよい!
これ以上の細かいことは散々他のところで解説されているのでggrks!


【で、どうすればいいの?】
ポイントは大きく分けて4つだ!
というか、先ほどの方法論の所で述べた通りである!
1.Deferredオブジェクトを生成する(関数内)
2.Promiseオブジェクトのステータスを変更する(関数内)
3.Promiseオブジェクトをreturnする(関数内)
4.返ってきたDeferred(Promise)オブジェクトの内容によって処理内容を変えるよう記載する(関数呼び出し元)


【だからどうすればいいんだよハゲ】
それぞれの書き方について、具体的に記載する!
なお、恐らくこの項目だけを見てもわかりづらいと思うので、後述するサンプルと見比べるのを推奨する!

1.Deferredオブジェクトを生成する(関数内)
------------------------------------------------------------------
var [オブジェクト名] = new $.Deferred;
------------------------------------------------------------------
これだけだ!
見ればわかると思うが、ここでは本当にただオブジェクトを生成しているだけである!
後の(関数内の)処理は、全てここで定義したDeferred型オブジェクトを利用する!

2.Promiseオブジェクトのステータスを変更する(関数内)
------------------------------------------------------------------
[オブジェクト名].resolve([引数]);
[オブジェクト名].reject([引数]);
------------------------------------------------------------------
言うまでもないと思うが、ここで言う「オブジェクト名」とは1で生成したオブジェクト名のことである!
この操作によって、Promiseオブジェクトの持つステータスが失敗か成功かへ変化する(実行中のステータスではなくなる)んだな!
成功の場合はresolveへ、失敗の場合はrejectへステータスを変化させよう!
例えば、こんな感じに書くイメージだ!
無論必ずしも両方書く必要はないので、成否関係ない場合はresolveだけ書いとけ!
-----------------------------------------------------------------
A; //非同期処理
if(成功){
 dfd.resolve("成功!");
}else{
 dfd.reject("失敗!");
}
------------------------------------------------------------------
ちなみに、ここで格納した引数は呼び出し元で使用することができるぞ!
returnする時ではなく、ステータスを更新する時に返り値を設定するので注意だ!
本当は「返り値を設定する」というと語弊がある気もするが、まぁなんか雰囲気で察しろ!

3.Promiseオブジェクトをreturnする(関数内)
------------------------------------------------------------------
return [オブジェクト名].promise();
------------------------------------------------------------------
やはりここでいう「オブジェクト名」とは1で生成した以下略!
これによって、2で格納したステータス(と返り値)を呼び出し元に送り、処理が終わったことを通知するんだな!

まとめると、関数側の流れはこんな感じだ!
-----------------------------------------------------------------
var dfd = new $.Deferred;

A; //非同期処理
if(成功){
 dfd.resolve("成功!");
}else{
 dfd.reject("失敗!");
}

return dfd.promise();
------------------------------------------------------------------



4.返ってきたPromiseオブジェクトの内容によって処理内容を変えるよう記載する(関数呼び出し元)
呼び出し元では、.thenメソッドというものを利用する!
ここでも細けぇ話は置いといて、返ってきたPromiseオブジェクトのステータスによって処理を変える(Promiseオブジェクトが返ってくるまで処理を実行しない)ようにするメソッドだと思っておけぃ!
------------------------------------------------------------------
.then(
 function([引数]) {
  //成功時の処理
 },
 function([引数]) {
  //失敗時の処理
 }
);
------------------------------------------------------------------
C言語ライク(?)に言うと、.thenはfuction型の引数2つを持つ関数みたいなもんだな!
第1引数はPromiseオブジェクトがresolevedの時に実行される関数で、第2引数はrejectedの時に実行される関数だ!
第2引数は省略可能で、その場合はresolvedの場合のみ処理が走るぞ!
無論、他で定義した関数を指定することも可能だ!
.thenを連結することで、順番に処理を実行していくことも可能だぞ!
なお、ここの[引数]で指定した変数で2で格納した返り値を受け取ることができるぞ!

まとめると、以下のような使い方ができるということだ!
------------------------------------------------------------------
sample()
.then(
 function([引数]) {
  //成功時の処理1
 },
 function([引数]) {
  //失敗時の処理1
 }
)
.then(
 function([引数]) {
  //成功時の処理2(失敗時は何もしない)
 }
)
.then( anotherFunc //成功時の処理3
);
------------------------------------------------------------------



【サンプル(修正版)】
以上を踏まえて、冒頭のクソサンプルを書き換える!
なお、本稿の目的を踏まえrejectの存在は無視しているので注意だ!
また、返り値の受け方についても書き方が変わっているので先述の説明を踏まえるように!
踏まえること多いな!

---------------------------------
var ret = sample("after");
document.write(ret);

function sample(str) {
 var tmp = "before";
 setTimeout(function() {
  tmp = str;
 }, 200);
 return tmp;
}
---------------------------------

---------------------------------
sample("after")
.then(
 function(ret) {
  document.write(ret);
 }
);

function sample(str) {
 var tmp = "before";
 var dfd = new $.Deferred;
 setTimeout(function() {
  tmp = str;
  dfd.resolve(tmp);
 }, 200);
 return dfd.promise();
}
---------------------------------

これで、想定通り"after"と出力されるクソプログラムとなったわけである!
dfd.resolve(tmp);が実行されるまでreturn dfd.promise();は実行されず、それを待ってから.then内の処理が実行されたわけだな!



【世界一シンプル(かもしれない)サンプル】
……とここまで書いて思ったが、こっちのサンプルの方がもっとシンプルだな!
関数もねぇ! 返り値もねぇ! 処理もそれほど走ってねぇ!

---------------------------------
var str = "before";

setTimeout(function() {
 str = "after";
}, 200);

//beforeと出力される
document.write(str);
---------------------------------

---------------------------------
var str = "before";
var dfd = new $.Deferred;

setTimeout(function() {
 str = "after";
 dfd.resolve(str);
}, 200);

dfd.then(
 function(ret) {
  //afterと出力される
  document.write(str);
 }
);
---------------------------------

なお、さんざん繰り返している通りDeferredはjQueryのライブラリなので当然jQueryの読み込みが必須なので注意されたし!
コールバックによる処理ならば純正JavaScriptのみでいけるはずだが、散々ディスられている上に見ただけで萎えたので今回は考慮していない!



以上だ!
間違っている点、勘違いしている点、効率の悪い点等、ツッコミをお待ちしている!
疑問も受け付けるが、答えられるかは不明だ!
スポンサーサイト

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

http://hamubane.blog.fc2.com/tb.php/3948-bf9d8a79
この記事にトラックバックする(FC2ブログユーザー)

«  | ホーム |  »

プロフィール

hamubane

Author:hamubane

たぶんライトノベル作家的なもの


Twitter

既刊情報

上の方は絶版じゃないよ!

お助けキャラに彼女がいるわけないじゃないですか2 (ファンタジア文庫)

最新コメント

最新記事

カレンダー

06 | 2018/07 | 08
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 - - - -

月別アーカイブ

カテゴリ

検索フォーム

RSSリンクの表示

リンク

このブログをリンクに追加する

ブロとも申請フォーム

この人とブロともになる

QRコード

QR