Edited at

JavaScriptの非同期とかコールバック地獄とかって結局何?

More than 1 year has passed since last update.


JavaScript難しい

みなさんjs書いてますか?僕は最近書くようになりました。

いままで非同期の言語って書いたことなかったのでコールバック関数なるものに翻弄されている毎日です。

僕が理解できた範囲のjsの非同期処理とコールバック関数というものをわかりやすくお伝えできればと思います。

初心者向けです。どちらかというとNode.js向けの話になると思います。


非同期処理って何?

今から当たり前の話をしますね。

非同期処理ではないプログラミング言語(PHPとかJavaとかPythonとか)だったら下の図のようなフローチャートがあった場合上から下に順番に実行していきますね?

ですが非同期処理のjsの場合は時間のかかる処理をとりあえず裏で実行し次の処理に行ってしまいます。

A、B、Cの処理の結果を待たずにプログラムは最後まで行きます。

ものすごく簡単に言うと、これが非同期処理です。

利点は実行が早いことです。


コールバック関数って何?

コールバック関数とはさきほどの時間がかかる処理というやつです。

コードで書くとこんな感じ。

hoge(function(){

// 何らかの処理
.
.
.
})

この場合はhogeというメソッドの引数に関数が書かれています。

コールバック関数とは引数の中に関数を書くことです。

関数の中に書いた関数の結果を待たずして次の処理に進みます。


何が問題?

以下のようなコードを考えましょう。

// hogeというテーブルの内容を全て取得する簡単なコードです。データベースの接続の部分は省略しました。

sql = 'select * from hoge'
connection.query(sql,function(error,query_results){
console.log(query_results);
});

次は失敗例です。

普通ならデータベースにクエリを投げるような処理は分離したいですよね?

ですがjsの場合うまくいきません。

ちなみにこれは僕が実際に書いて、何でやって思ったコードです。

// fugaという関数に分離しました

function fuga(sql){
connection.query(sql,function(error,query_results){
return query_results;
});
}

sql = 'select * from hoge'
var result = fuga(sql);

console.log(result);

このコードの場合、コールバック関数でリターンをしていますが関数の中の関数なのでリターンは返す場所がよく分からなくなっています。

コールバック関数は基本的にリターンができません、ゲボボボ。

このままではコードを分離できないので力ずくで分離します。

function fuga(sql){

connection.query(sql,function(error,query_results){
// グローバル変数fooの中に結果を入れます
foo = query_results
});
}

sql = 'select * from wp_users'
// 適当にfooというグローバル変数を用意します。
var foo = '';
fuga(sql);

console.log(foo);

このconsole.logの結果はfooが空になります

foo = query_resultsの前にconsole.log(foo);が実行されます、なぜかというとクエリの結果を待つ前に最後まで実行してしまうからです。

よってfooという変数は空のまま実行が終わります。

関数のモジュール化すらさせてくれんのかこの言語は,,,

どうしてもfooにクエリの実行結果を入れたい場合はsetTimeoutなどを使ってクエリの実行を待ってやるとうまく行きます。

なんだこれはたまげたなぁ

なのでjsでは次のように書くのが1番手っ取り早く間違い無いです

function fuga(sql){

connection.query('select * from wp_users',function(error,results){
// モジュール化などせず続きの処理を延々とコールバック関数内に書く
.
.
.
});
}


コールバック地獄の正体

さきほどのフローチャートをもう少し考えて見ましょう。

入出力を考えてみます。

これは普通のプログラミング言語の場合です。それぞれの入出力が前後の処理に依存しています。

これは非常にわかりやすいっていうか、当たり前ですね。

次はjsの場合を考えて見ましょう。

A,B,Cの処理はコールバック関数としましょう。

ここで気をつけないといけないのがコールバック関数は処理の内部に出力があり、コールバック関数内で処理の結果を待たないと次の処理の入力がもらえない点です。

図のように処理が処理の内部へと潜って行きます。

これが深くなりすぎた状態を所謂コールバック地獄と言います。


Promiseを使って同期処理する

最近は脱コールバック地獄のためにPromiseというやつを使うようです。

詳しい使い方はいろんな人が紹介しているのでそちらを見てもらったほうがいいでしょう。

ここではPromiseのざっくりとしてイメージを紹介します。

簡単にいうとPromiseはjsの同期処理を行ってくれます。

それぞれの処理をPromiseが実行し処理が終わるまで待ってくれます。

ですがこの図を見ただけではPromiseはjsの非同期処理という利点を無くしているようにも思えます。

次の図のように考えましょう。

この図では処理Aを拡大し、その内部について考えます。

簡単にいうと同期を取らないといけない部分ではPromiseを使い、それ以外は非同期にすることでjsのいい部分も使いましょうということです。


結論

jsはクソめんどくさい。

だれかjsのモジュール化のいい方法教えてください。