[解決済み] 関数内で変数を変更した後、変数が変更されないのはなぜですか?- 非同期コードリファレンス
質問
次のような例がありますが、なぜ
outerScopeVar
は、すべてのケースで未定義ですか?
var outerScopeVar;
var img = document.createElement('img');
img.onload = function() {
outerScopeVar = this.width;
};
img.src = 'lolcat.png';
alert(outerScopeVar);
var outerScopeVar;
setTimeout(function() {
outerScopeVar = 'Hello Asynchronous World!';
}, 0);
alert(outerScopeVar);
// Example using some jQuery
var outerScopeVar;
$.post('loldog', function(response) {
outerScopeVar = response;
});
alert(outerScopeVar);
// Node.js example
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
outerScopeVar = data;
});
console.log(outerScopeVar);
// with promises
var outerScopeVar;
myPromise.then(function (response) {
outerScopeVar = response;
});
console.log(outerScopeVar);
// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
outerScopeVar = pos;
});
console.log(outerScopeVar);
なぜか出力される
undefined
は、これらのすべての例で?回避策ではなく、知りたいのは
なぜ
ということが起こっています。
注 の正規の質問です。 JavaScriptの非同期性 . この質問を改善し、コミュニティが共感できるようなより単純化された例を自由に追加してください。
どのように解決するのですか?
答えは一言。 非同期性 .
序文
このトピックは、ここStack Overflowで、少なくとも数千回繰り返されてきました。したがって、まず最初に、非常に有用なリソースをいくつか紹介したいと思います。
-
非同期呼び出しからレスポンスを返すには?"に対する@Felix Klingの回答。 . 同期と非同期のフローを説明した彼の素晴らしい回答や、"Restructure code"のセクションをご覧ください。
Benjamin Gruenbaumも同じスレッドで非同期性の説明に力を注いでいます。 -
fs.readFile"からデータを取得する」に対する@Matt Eschの回答です。 も、非同期性を非常によくわかりやすく説明しています。
目の前の質問に対する答え
まず、共通の動作をたどってみましょう。すべての例で
outerScopeVar
の内部で変更されます。
機能
. その関数は明らかにすぐには実行されず、代入されたり引数として渡されたりしています。これが、私たちが
コールバック
.
さて、問題はそのコールバックがいつ呼ばれるかということです。
それはケースによって異なります。もう一度、よくある動作をトレースしてみましょう。
-
img.onload
という場合があります。 いつかは 画像の読み込みに成功したとき(そして成功した場合)。 -
setTimeout
と呼ばれることがあります。 いつかは によってタイムアウトがキャンセルされなかった場合、遅延時間が経過した後にclearTimeout
. 注0
遅延時間として、すべてのブラウザでタイムアウト遅延の最小値(HTML5 仕様では 4ms と指定されています)が設定されています。 -
jQuery
$.post
のコールバックが呼び出されることがあります。 将来のある時点で Ajaxリクエストが正常に完了したとき(およびその場合)。 -
Node.jsの
fs.readFile
と呼ばれることがあります。 将来的には ファイルの読み込みに成功したとき、またはエラーが発生したとき。
すべてのケースで、コールバックが実行されます。 将来のいつか . この「将来のいつか」というのが 非同期フロー .
非同期実行は、同期フローから押し出される。つまり、非同期コードは 決して 同期コードスタックが実行されている間に実行されます。これがJavaScriptがシングルスレッドであることの意味です。
具体的には、JSエンジンがアイドル状態のとき、つまり同期コードのスタックを実行していないとき、非同期コールバックの引き金となったかもしれないイベント(たとえば、タイムアウトが切れた、ネットワーク応答を受け取った)をポーリングして、次々に実行する。これは、次のように見なされます。 イベントループ .
つまり、手書きの赤い図形で示された非同期コードは、それぞれのコードブロックに残っている同期コードがすべて実行された後にのみ、実行することができるのです。
要するに、コールバック関数は同期的に作成されるが、非同期的に実行されるのである。ただ、非同期関数の実行は、それが実行されたことがわかるまでは当てにならないし、どうすればいいのか?
実にシンプルです。非同期関数の実行に依存するロジックは、この非同期関数の内部から起動/呼び出しする必要があります。例えば
alert
と
console.log
のように、コールバック関数の内部でも、その時点で結果が得られているため、期待通りの結果が出力されます。
独自のコールバックロジックを実装する
非同期関数の結果に対してより多くのことを行う必要があったり、非同期関数が呼び出された場所によって異なることを行う必要があったりすることがよくあります。もう少し複雑な例に取り組んでみましょう。
var outerScopeVar;
helloCatAsync();
alert(outerScopeVar);
function helloCatAsync() {
setTimeout(function() {
outerScopeVar = 'Nya';
}, Math.random() * 2000);
}
注
を使っています。
setTimeout
をランダムな遅延を伴う一般的な非同期関数として、Ajaxにも同じ例が適用されます。
readFile
,
onload
といった非同期な流れになります。
この例は明らかに他の例と同じ問題を抱えています。非同期関数が実行されるまで待っていないのです。
それでは、独自のコールバックシステムを実装してみましょう。まず最初に、あの醜い
outerScopeVar
これはこの場合、全く役に立たない。次に、関数の引数を受け取るパラメータを追加します。これがコールバックです。非同期処理が終了したら、このコールバックを呼び出し、結果を渡す。実装は以下の通り(コメントを順番に読んでください)。
// 1. Call helloCatAsync passing a callback function,
// which will be called receiving the result from the async operation
helloCatAsync(function(result) {
// 5. Received the result from the async function,
// now do whatever you want with it:
alert(result);
});
// 2. The "callback" parameter is a reference to the function which
// was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
// 3. Start async operation:
setTimeout(function() {
// 4. Finished async operation,
// call the callback passing the result as argument
callback('Nya');
}, Math.random() * 2000);
}
上記の例のコードスニペット。
// 1. Call helloCatAsync passing a callback function,
// which will be called receiving the result from the async operation
console.log("1. function called...")
helloCatAsync(function(result) {
// 5. Received the result from the async function,
// now do whatever you want with it:
console.log("5. result is: ", result);
});
// 2. The "callback" parameter is a reference to the function which
// was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
console.log("2. callback here is the function passed as argument above...")
// 3. Start async operation:
setTimeout(function() {
console.log("3. start async operation...")
console.log("4. finished async operation, calling the callback, passing the result...")
// 4. Finished async operation,
// call the callback passing the result as argument
callback('Nya');
}, Math.random() * 2000);
}
実際の使用例では、ほとんどの場合、DOM API とほとんどのライブラリがすでにコールバック機能を提供しています (
helloCatAsync
を実装しています)。コールバック関数を渡して、それが同期フローから外れて実行されることを理解し、それに対応するようにコードを再構築すればよいのです。
また、非同期であるため、以下のようなことができないことにお気づきでしょう。
return
非同期コールバックは、同期コードがすでに実行を終了したずっと後に実行されるため、コールバックが定義されている同期フローに非同期フローから値を戻すことができます。
の代わりに
return
非同期コールバックから値を取得するには、コールバックパターンを利用する必要があります。プロミス
プロミス
を維持する方法がありますが コールバック地獄 を使用した場合、プロミスはますます人気が高まり、現在ES6で標準化されつつあります( プロミス - MDN ).
プロミス(別名:フューチャー)は、非同期コードをより直線的に、つまり気持ちよく読むことができますが、その機能全体を説明することは、この質問の範囲外です。代わりに、私はこれらの素晴らしいリソースを残しておきます。
JavaScriptの非同期性についてのその他の読み物
- The Art of Node - コールバック は、非同期コードとコールバックについて、バニラJSの例とNode.jsのコードも含めて、非常によく説明しています。
<ブロッククオート
注 この回答はコミュニティWikiとして、少なくとも100の評判を持つ誰もが編集し、改善することができます。この回答を改善したり、まったく新しい回答を投稿したりすることもできますので、ご自由にどうぞ。
この質問を正規のトピックにして、Ajaxとは無関係の非同期性の問題に答えたいと思います( AJAX呼び出しからレスポンスを返すには? そのため、このトピックはできるだけ良い、役に立つものにするためにあなたの助けが必要です!
関連
-
fetch ネットワークリクエストラッパーの説明例
-
Vue+ElementUIによる大規模なフォームの処理例
-
vue for 登録ページ効果 vue for sms 認証コードログイン
-
[解決済み】Node.jsで "Cannot find module "エラーを解決するには?
-
[解決済み】「X-Frame-Options」を「SAMEORIGIN」に設定したため、フレームでの表示を拒否された。
-
[解決済み】ExpressJS : res.redirect()が期待通りに動かない?
-
[解決済み】 Uncaught TypeError : undefined のプロパティ 'replace' を読み取れない In Grid
-
[解決済み】なぜ、javascriptのすべての関数の後にセミコロンを使用しなければならないのですか?
-
[解決済み】非同期関数が値の代わりにPromise { <pending> }を返すのはなぜですか?
-
[解決済み] ある関数内でletを使って宣言した変数が、他の関数で利用可能になる場合と、参照エラーになる場合があるのはなぜですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Vueの要素ツリーコントロールに破線を追加する説明
-
jsを使った簡単な照明スイッチのコード
-
Vueでルートネスティングを実装する例
-
[解決済み】SyntaxError: JSONの位置1に予期しないトークンoがある。
-
[解決済み】Uncaught SyntaxError: JSONの位置0に予期しないトークンuがあります。
-
[解決済み】Node.js getaddrinfo ENOTFOUND
-
[解決済み】Node.jsで "Cannot find module "エラーを解決するには?
-
[解決済み】 `string.split is not a function` というエラーの原因は何ですか?
-
nullのプロパティinnerHTMLを読み取れません エラーメッセージ
-
JavaScriptのgetElementById()メソッド入門