web-dev-qa-db-ja.com

非同期Javascript実行はどのように行われますか?そして、いつreturnステートメントを使用しないのですか?

// synchronous Javascript
var result = db.get('select * from table1');
console.log('I am syncronous');

// asynchronous Javascript 
db.get('select * from table1', function(result){
    // do something with the result
});
console.log('I am asynchronous')

同期コードでは、console.log()は結果がdbからフェッチされた後に実行されますが、非同期コードではconsole.log()はdb.get()が結果をフェッチする前に実行されます。

さて、私の質問は、実行は非同期コードの舞台裏でどのように行われるのですか、なぜそれは非ブロッキングなのですか?

Ecmascript 5標準を検索して、非同期コードのしくみを理解しましたが、標準全体でWord非同期を見つけることができませんでした。

また、nodebeginner.orgから、イベントループをブロックするため、returnステートメントを使用しないでください。しかし、nodejs apiとサードパーティのモジュールには、どこにでもreturnステートメントが含まれています。では、いつreturnステートメントを使用し、いつ使用しないのですか?

誰かがこれに光を投げることはできますか?

37
ekanna

まず第一に、関数をパラメーターとして渡すことは、呼び出している関数に、将来この関数を呼び出して欲しいことを伝えることです。いつ正確に呼び出されるかは、関数の実行内容に依存します。

関数が一部のネットワーキングを実行していて、その関数が非ブロッキングまたは非同期に構成されている場合、関数が実行され、ネットワーキング操作が開始され、呼び出された関数はすぐに戻り、残りのインラインJavaScriptコードはその関数が実行されます。その関数から値を返すと、パラメーターとして渡した関数が呼び出される(ネットワーク操作がまだ完了していない)ずっと前に、すぐに戻ります。

一方、ネットワーキング操作はバックグラウンドで進んでいます。リクエストを送信し、レスポンスをリッスンしてから、レスポンスを収集します。ネットワーク要求が完了し、応答が収集されると、呼び出された元の関数がパラメーターとして渡された関数を呼び出します。これは、ネットワーク操作が完了するまでにかかった時間に応じて、数ミリ秒後になったり、数分後になったりする場合があります。

理解しておくべき重要なことは、あなたの例では、db.get()関数呼び出しが完了してから長い時間が経過し、コードが実行された後にコードが順番に実行されることです。完了していないのは、その関数にパラメーターとして渡した内部の無名関数です。それは、ネットワーキング関数が終了するまで、JavaScript関数クロージャに保持されます。

多くの人々を混乱させる1つのことは、匿名関数がdb.getへの呼び出しの中で宣言され、その一部であるように見え、db.get()が実行されると、これはすることもできますが、そうではありません。多分それがこのように表された場合、そのようには見えないでしょう:

function getCompletionfunction(result) {
    // do something with the result of db.get
}

// asynchronous Javascript 
db.get('select * from table1', getCompletionFunction);

次に、db.getがすぐに戻り、getCompletionFunctionが将来呼び出されることがより明白になる場合があります。このように書くことはお勧めしませんが、実際に起こっていることを説明する手段としてこのフォームを表示するだけです。

理解に値するシーケンスは次のとおりです。

console.log("a");
db.get('select * from table1', function(result){
    console.log("b");
});
console.log("c");

デバッガーコンソールに表示されるのは次のとおりです。

a
c
b

「a」が最初に発生します。次に、db.get()は操作を開始し、すぐに戻ります。したがって、次に「c」が発生します。次に、db.get()操作が実際に将来完了すると、「b」が発生します。


ブラウザーでの非同期処理のしくみについては、 JavaScriptはどのように処理するかAJAX応答をバックグラウンドで処理するか? を参照してください。

29
jfriend00

jfriend00の回答 は、ほとんどのユーザーに適用される非同期を説明していますが、コメントで実装の詳細が必要なようです。

[…]この種の機能を実現するために、Ecmascript仕様の実装部分を説明する擬似コードを任意の機関が記述できますか? JSの内部をよりよく理解するため。

ご存知のように、関数はその引数をグローバル変数に格納できます。数のリストと数を追加する関数があるとしましょう:

var numbers = [];
function addNumber(number) {
    numbers.Push(number);
}

以前と同じnumbers変数を参照している限り、いくつかの数値を追加すると、以前に追加した数値にアクセスできます。

JavaScript実装は、数値を格納するのではなく、関数(具体的には、コールバック関数)を格納することを除いて、おそらく同じようなことをします。

イベントループ

多くのアプリケーションの中心にあるのは、イベントループと呼ばれるものです。基本的には次のようになります。

  • 永久にループする:
    • イベントを取得し、存在しない場合はブロックする
    • プロセスイベント

あなたの質問のようにデータベースクエリを実行したいとしましょう:

db.get("select * from table", /* ... */);

そのデータベースクエリを実行するには、ネットワーク操作を実行する必要があります。ネットワーク操作にはかなりの時間がかかる可能性があり、その間、プロセッサは待機しているため、他の作業を行うのではなく待機するのではなく、実行するタイミングを通知するだけでよいと考えるのが理にかなっています。その間に他のもの。

簡単にするために、送信は同期的にブロック/ストールすることは決してないものとします。

getの機能は次のようになります。

  • リクエストの一意の識別子を生成する
  • リクエストを送信します(ここでも、簡単にするために、これがブロックしないと仮定します)
  • グローバル識別子/ハッシュテーブル変数に(識別子、コールバック)ペアを格納する

getが行うことはそれだけです。受信ビットは何も実行せず、コールバックを呼び出す責任もありません。これは、プロセスイベントビットで発生します。プロセスイベントビットは、(部分的に)次のようになります。

  • イベントはデータベース応答ですか?その場合:
    • データベースの応答を解析する
    • ハッシュテーブルの応答で識別子を検索して、コールバックを取得します
    • 受け取った応答でコールバックを呼び出す

実生活

実際には、少し複雑ですが、全体的なコンセプトはそれほど変わりません。たとえば、データを送信する場合は、オペレーティングシステムの送信ネットワークバッファに十分なスペースができるまで待たないと、データを追加できません。データを読み取るときに、複数のチャンクでデータを取得する場合があります。プロセスイベントビットはおそらく1つの大きな関数ではありませんが、それ自体が一連のコールバックを呼び出すだけです(コールバックはさらにコールバックにディスパッチされる、などなど)。

実際と例の実装の詳細は少し異なりますが、概念は同じです。「何かを行う」ことから始め、作業が完了すると、何らかのメカニズムを通じてコールバックが呼び出されます。

17
icktoofay