JavaScriptは常に非同期であるという印象を受けました。しかし、そうでない状況(つまり、DOM操作)があることを知りました。いつ同期するか、いつ非同期になるかについて、適切なリファレンスはありますか?jQueryはこれにまったく影響しますか?
JavaScriptは常に非同期であるという印象を受けました。しかし、そうでない状況(つまり、DOM操作)があることを知りました。いつ同期するか、いつ非同期になるかについて、適切なリファレンスはありますか?jQueryはこれにまったく影響しますか?
回答:
JavaScriptは常に同期的でシングルスレッドです。ページでJavaScriptコードブロックを実行している場合、そのページの他のJavaScriptは現在実行されません。
JavaScriptは、たとえばAjax呼び出しを実行できるという意味でのみ非同期です。Ajax呼び出しは実行を停止し、他のコードは呼び出しが(成功またはその他の方法で)戻るまで実行でき、その時点でコールバックは同期的に実行されます。この時点では他のコードは実行されません。現在実行中の他のコードに割り込むことはありません。
JavaScriptタイマーは、これと同じ種類のコールバックで動作します。
JavaScriptを非同期として記述するのは、おそらく誤解を招く可能性があります。JavaScriptは同期的であり、さまざまなコールバックメカニズムでシングルスレッド化されていると言う方がより正確です。
jQueryには、Ajax呼び出しでオプションを使用して、それらを同期的に作成するasync: false
オプションがあります。初心者は、これをより慣れ親しんでいる可能性のあるより伝統的なプログラミングモデルを可能にするため、これを誤って使用したくなるかもしれません。問題があるのは、このオプションでは、すべてのイベントハンドラーやタイマーを含め、完了するまでページ上のすべての JavaScript がブロックされるためです。
JavaScriptはシングルスレッドであり、同期実行モデルを備えています。シングルスレッドとは、一度に1つのコマンドが実行されることを意味します。同期とは、一度に1つずつ、つまり、コードが表示されるために一度に1行のコードが実行されることを意味します。したがって、JavaScriptでは一度に1つのことが起こります。
実行コンテキスト
JavaScriptエンジンは、ブラウザー内の他のエンジンと対話します。JavaScript実行スタックでは、下部にグローバルコンテキストがあり、関数を呼び出すと、JavaScriptエンジンがそれぞれの関数の新しい実行コンテキストを作成します。呼び出された関数が終了すると、その実行コンテキストがスタックからポップされ、次に次の実行コンテキストがポップされます。
例えば
function abc()
{
console.log('abc');
}
function xyz()
{
abc()
console.log('xyz');
}
var one = 1;
xyz();
上記のコードでは、グローバル実行コンテキストが作成され、このコンテキストvar one
に格納され、その値は1になります... xyz()呼び出しが呼び出されると、新しい実行コンテキストが作成され、変数を定義した場合xyz関数では、これらの変数はxyz()の実行コンテキストに格納されます。xyz関数でabc()を呼び出し、次にabc()実行コンテキストが作成されて実行スタックに配置されます... abc()が終了すると、コンテキストがスタックからポップされ、次にxyz()コンテキストがスタックすると、グローバルコンテキストがポップされます...
次に非同期コールバックについてです。非同期とは、一度に複数のことを意味します。
実行スタックと同様に、イベントキューがあります。JavaScriptエンジンのイベントに関する通知を受け取りたい場合は、そのイベントをリッスンして、そのイベントをキューに入れることができます。たとえば、Ajax要求イベント、またはHTTP要求イベントです。
上記のコード例に示すように、実行スタックが空の場合は常に、JavaScriptエンジンは定期的にイベントキューを調べ、通知するイベントがあるかどうかを確認します。たとえば、キューにはajaxリクエストとHTTPリクエストの2つのイベントがありました。また、そのイベントトリガーで実行する必要のある関数があるかどうかも確認します。そのため、JavaScriptエンジンはイベントについて通知を受け、そのイベントで実行するそれぞれの関数を認識しています...したがって、JavaScriptエンジンは、例の場合、ハンドラー関数。例:AjaxHandler()が呼び出され、常に関数が呼び出されると、その実行コンテキストが実行コンテキストに配置され、関数の実行が終了し、イベントajaxリクエストもイベントキューから削除されます。 ... AjaxHandler()が完了すると、実行スタックは空になるので、エンジンは再びイベントキューを調べ、次にキューにあったHTTPリクエストのイベントハンドラー関数を実行します。イベントキューは、実行スタックが空の場合にのみ処理されることに注意してください。
たとえば、JavaScriptエンジンによる実行スタックとイベントキューの処理を説明する以下のコードを参照してください。
function waitfunction() {
var a = 5000 + new Date().getTime();
while (new Date() < a){}
console.log('waitfunction() context will be popped after this line');
}
function clickHandler() {
console.log('click event handler...');
}
document.addEventListener('click', clickHandler);
waitfunction(); //a new context for this function is created and placed on the execution stack
console.log('global context will be popped after this line');
そして
<html>
<head>
</head>
<body>
<script src="program.js"></script>
</body>
</html>
次に、Webページを実行してページをクリックし、コンソールに出力を表示します。出力は
waitfunction() context will be popped after this line
global context will be emptied after this line
click event handler...
JavaScriptエンジンは、実行コンテキスト部分で説明されているようにコードを同期的に実行しており、ブラウザーは非同期でイベントキューに物事を入れています。したがって、完了するまでに非常に長い時間がかかる関数は、イベント処理を中断する可能性があります。イベントのようにブラウザーで発生することはJavaScriptによってこのように処理されます。実行されるはずのリスナーがある場合、実行スタックが空のときにエンジンがそれを実行します。また、イベントは発生順に処理されるため、非同期の部分は、エンジンの外部で何が発生しているか、つまり、外部のイベントが発生したときにエンジンが何をすべきかについてです。
したがって、JavaScriptは常に同期しています。
JavaScriptはシングルスレッドであり、通常の同期コードフロー実行で常に作業しています。
JavaScriptが持つことができる非同期動作の良い例は、イベント(ユーザー操作、Ajaxリクエストの結果など)とタイマー、基本的にいつでも発生する可能性があるアクションです。
次の記事をご覧になることをお勧めします。
この記事は、JavaScriptのシングルスレッドの性質、タイマーが内部でどのように機能するか、およびJavaScriptの非同期実行がどのように機能するかを理解するのに役立ちます。
JSがどのように機能するかを本当に理解している人にとっては、この質問は気が遠くなるかもしれませんが、JSを使用するほとんどの人はそれほど深い洞察力を持っていません(そして必ずしも必要ではありません)。その観点から答えてみてください。
JSは、コードの実行方法が同期しています。各行は、それが完了する前にその行の後にのみ実行され、その行が完了後に関数を呼び出す場合は、その他...
混乱の主な原因は、ブラウザーがJSにいつでもより多くのコードを実行するように指示できることです(コンソールからページ上でより多くのJSコードを実行する方法と同様)。例として、JSにはコールバック関数があり、その目的はJSが非同期に動作できるようにすることです。これにより、実行されたJS関数(IE GET
呼び出し)が応答を返すのを待っている間、JSの追加の部分を実行できます。ブラウザーはその時点で応答を返し、イベントループ(ブラウザー)はコールバック関数を呼び出すJSコードを実行します。
イベントループ(ブラウザー)は、任意の時点で実行される追加のJSを入力できるため、JSは非同期です(ブラウザーがJSコードを入力する主な原因は、タイムアウト、コールバック、およびイベントです)。
これが誰かに役立つほど明確であることを願っています。
「非同期」という用語はわずかに異なる意味で使用されている可能性があり、実際にはそうではありませんが、ここでは一見矛盾する回答になります。非同期のウィキペディアには次の定義があります。
コンピュータプログラミングにおける非同期とは、メインプログラムフローとは無関係に発生するイベントと、そのようなイベントを処理する方法を指します。これらは、信号の到着などの「外部」イベント、またはプログラムのブロックと同時に結果を待たずにプログラムの実行と同時に行われるプログラムによって引き起こされたアクションである可能性があります。
非JavaScriptコードは、そのような「外部」イベントを一部のJavaScriptのイベントキューにキューイングできます。しかし、それはそれが行くところです。
外部からの中断はありませんスクリプトで他のJavaScriptコードを実行するために、JavaScriptコードの実行が。JavaScriptの断片は次々に実行され、順序は各イベントキュー内のイベントの順序とそれらのキューの優先順位によって決定されます。
たとえば、次のコードが実行されている間は、(同じスクリプト内の)他のJavaScriptが実行されないことが確実になります。
let a = [1, 4, 15, 7, 2];
let sum = 0;
for (let i = 0; i < a.length; i++) {
sum += a[i];
}
つまり、JavaScript にはプリエンプションはありません。イベントキューに何があっても、それらのイベントの処理は、そのようなコードの一部が実行されるまで待機する必要があります。EcmaScript仕様では、セクション8.4のジョブとジョブキューで次のように述べています。
ジョブの実行は、実行中の実行コンテキストがなく、実行コンテキストスタックが空の場合にのみ開始できます。
他の人がすでに書いているように、JavaScriptで非同期が発生する状況がいくつかあり、他のJavaScriptコードが実行されていない場合にのみJavaScriptが実行されるイベントキューが常に含まれます。
setTimeout()
:エージェント(ブラウザなど)は、タイムアウトの期限が切れると、イベントをイベントキューに入れます。時間の監視とキューへのイベントの配置は非JavaScriptコードによって行われるため、一部のJavaScriptコードの潜在的な実行と並行して行われることを想像できます。ただし、に提供されるコールバックはsetTimeout
、現在実行中のJavaScriptコードが最後まで実行され、適切なイベントキューが読み取られている場合にのみ実行できます。
fetch()
:エージェントは、OS機能を使用してHTTPリクエストを実行し、着信応答を監視します。繰り返しますが、この非JavaScriptタスクは、まだ実行中の一部のJavaScriptコードと並行して実行される場合があります。ただし、によって返されるプロミスを解決するプロミス解決プロシージャはfetch()
、現在実行中のJavaScriptが完了したときにのみ実行できます。
requestAnimationFrame()
:ブラウザーのレンダリングエンジン(非JavaScript)は、ペイント操作を実行する準備ができると、JavaScriptキューにイベントを配置します。JavaScriptイベントが処理されると、コールバック関数が実行されます。
queueMicrotask()
:すぐにイベントをマイクロタスクキューに入れます。コールスタックは、コールスタックが空でそのイベントが消費されたときに実行されます。
さらに多くの例がありますが、これらの関数はすべて、コアEcmaScriptではなく、ホスト環境によって提供されます。コアEcmaScriptを使用すると、を使用してPromiseジョブキューに同期的にイベントを配置できますPromise.resolve()
。
ECMAScriptのは、次のような、非同期パターンをサポートするために、いくつかの言語構造を提供しyield
、async
、await
。ただし、間違いはありません。外部イベントによってJavaScriptコードが中断されることはありません。ことを「中断」yield
とawait
関数呼び出しから戻ると、後でその実行コンテキストを復元するだけで制御し、事前に定義された方法で提供しているように見える、いずれかのJSコードによって(の場合yield
の場合)、またはイベントキュー(await
)。
JavaScriptコードがDOM APIにアクセスすると、場合によっては、DOM APIが1つ以上の同期通知をトリガーすることがあります。そして、コードにそれをリッスンするイベントハンドラーがある場合、それが呼び出されます。
これはプリエンプティブな同時実行として発生する可能性がありますが、そうではありません。イベントハンドラが戻ると、DOM APIも最終的に戻り、元のJavaScriptコードが続行されます。
その他の場合、DOM APIは適切なイベントキューにイベントをディスパッチするだけで、JavaScriptはコールスタックが空になるとそれを取得します。
参照してください。同期および非同期のイベントを
すべてのケースで同期です。
でスレッドをブロックする例Promises
:
const test = () => new Promise((result, reject) => {
const time = new Date().getTime() + (3 * 1000);
console.info('Test start...');
while (new Date().getTime() < time) {
// Waiting...
}
console.info('Test finish...');
});
test()
.then(() => console.info('Then'))
.finally(() => console.info('Finally'));
console.info('Finish!');
出力は次のようになります。
Test start...
Test finish...
Finish!