Node.js
非同期プログラミング
サーチ…
前書き
Nodeは、すべてが非同期的に動作するプログラミング言語です。以下に、非同期作業の典型的な例といくつかの例を見つけることができます。
構文
- doSomething([args]、function([argsCB]){/ *終了時に何かする* /});
- doSomething([args]、([argsCB])=> {/ *終了時に何かする* /});
コールバック関数
JavaScriptのコールバック関数
JavaScriptではコールバック関数が一般的です。 関数はファーストクラスの市民であるため、JavaScriptではコールバック関数が使用できます。
同期コールバック。
コールバック関数は、同期または非同期にすることができます。非同期コールバック関数はより複雑なので、ここではシンクロナイゼーションコールバック関数の簡単な例を示します。
// a function that uses a callback named `cb` as a parameter
function getSyncMessage(cb) {
cb("Hello World!");
}
console.log("Before getSyncMessage call");
// calling a function and sending in a callback function as an argument.
getSyncMessage(function(message) {
console.log(message);
});
console.log("After getSyncMessage call");
上記のコードの出力は次のとおりです。
> Before getSyncMessage call
> Hello World!
> After getSyncMessage call
まず、上記のコードがどのように実行されるかについて説明します。これは、コールバックの概念をすでに理解していない人にとっては、この段落をスキップすることが自由であるということをすでに理解している方にとっては、より多くのことです。最初にコードが解析され、次にBefore getSyncMessage call
をコンソールに出力Before getSyncMessage call
行6が実行されます。次に、 getSyncMessage
関数内のcb
という名前のパラメータの引数として、関数getSyncMessage
を無名関数で送信する行8が実行されます。今実行されたのは、渡されたばかりの関数cb
を実行する3行目のgetSyncMessage
関数であり、この呼び出しは、渡された無名関数のmessage
という名前のparamに引数文字列 "Hello World"を送ります。実行は9行目に進み、 Hello World!
をログに記録しHello World!
コンソールに接続します。その後、実行は、 呼び出しスタックを終了するプロセス( また 、 参照 )を10行、次に4行、最後に11行目に戻します。
一般的なコールバックについて知るべき情報:
- 関数にコールバックとして送信する関数は、0回、1回、または複数回呼び出すことができます。それはすべて実装に依存します。
- コールバック関数は、同期的または非同期的に呼び出すことができ、場合によっては、同期的および非同期的に呼び出すことができる。
- 通常の関数と同様に、関数にパラメータを渡す名前は重要ではありませんが、順序はです。たとえば、8行目では、パラメータ
message
statement
、msg
という名前statement
付けることができました。あるいは、あなたがjellybean
ような無意味なものになっているとします。コールバックにどのパラメータが送られるのかを知っていて、適切な名前で正しい順序で取得できるようにする必要があります。
非同期コールバック。
JavaScriptに関して注意すべきことは、デフォルトでは同期的ですが、非同期にできるAPI(ブラウザ、Node.jsなど)があります(詳細はこちら )。
コールバックを受け入れるJavaScript環境で非同期の一般的なもの:
- イベント
- setTimeout
- setInterval
- フェッチAPI
- 約束
また、上記の関数のいずれかを使用する関数は、コールバックをとる関数でラップすることができます。ただし、コールバックは非同期コールバックになります(ただし、コールバックをとる関数で約束をラッピングすることは、約束事に対処するより好ましい方法があります)。
そのような情報を与えられれば、上記のような非同期関数を構築することができます。
// a function that uses a callback named `cb` as a parameter
function getAsyncMessage(cb) {
setTimeout(function () { cb("Hello World!") }, 1000);
}
console.log("Before getSyncMessage call");
// calling a function and sending in a callback function as an argument.
getAsyncMessage(function(message) {
console.log(message);
});
console.log("After getSyncMessage call");
これはコンソールに次のものを表示します:
> Before getSyncMessage call
> After getSyncMessage call
// pauses for 1000 ms with no output
> Hello World!
行の実行は、「getSyncMessage呼び出しの前に」行6のログに移動します。その後、実行は8行目に戻り、パラメータcb
コールバックを使用してgetAsyncMessageを呼び出します。 3行目が実行され、最初の引数としてコールバック、2番目の引数として300という値を持つsetTimeoutが呼び出されます。 setTimeout
はそのコールバックに何があってもそのコールバックを保持するので、後で1000ミリ秒で呼び出すことができますが、タイムアウトを設定してから1000ミリ秒を一時停止する前に、実行を中止した場所に戻して4行目に戻ります次に11行目を停止してから1秒間ポーズし、setTimeoutはコールバック関数を呼び出します。このコールバック関数は3行目にgetAsyncMessages
ます。この場合、 getAsyncMessages
コールバックはパラメーターmessage
値「Hello World」で呼び出され、9行目。
Node.jsのコールバック関数
NodeJSは非同期コールバックを持ち、通常は関数err
とdata
と呼ばれることがある関数に2つのパラメータを共通に供給します。ファイルのテキストを読む例。
const fs = require("fs");
fs.readFile("./test.txt", "utf8", function(err, data) {
if(err) {
// handle the error
} else {
// process the file text given with data
}
});
これは、1回と呼ばれるコールバックの例です。
それはあなたがちょうどそれを記録したり投げたりしても何らかの形でエラーを処理するのがよい習慣です。あなたが投げたり戻ったり、投げたり戻ったりするようなことをして、ifで現在の関数の実行を止める限り、インデントを減らすために削除することができるelseは必要ありません。
err
、 data
を見るのが一般的かもしれませんが、コールバックがそのパターンを使用することは常にerr
とは限りませんが、ドキュメントを見るのが一番です。
もう1つのコールバックの例は、Expressライブラリ(express 4.x)にあります。
// this code snippet was on http://expressjs.com/en/4x/api.html
const express = require('express');
const app = express();
// this app.get method takes a url route to watch for and a callback
// to call whenever that route is requested by a user.
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000);
この例は、複数回呼び出されるコールバックを示しています。コールバックには、 req
とres
という名前のparamsという2つのオブジェクトが用意されています。これらの名前はリクエストとレスポンスにそれぞれ対応し、リクエストを表示してユーザーに送信するレスポンスを設定する方法を提供します。
ご覧のとおり、JavaScriptで同期と非同期のコードを実行するためにコールバックを使用するさまざまな方法があり、JavaScriptを通じてコールバックは非常に遍在しています。
コード例
質問:以下のコードの出力は何ですか?なぜですか?
setTimeout(function() {
console.log("A");
}, 1000);
setTimeout(function() {
console.log("B");
}, 0);
getDataFromDatabase(function(err, data) {
console.log("C");
setTimeout(function() {
console.log("D");
}, 1000);
});
console.log("E");
出力:これは確かに知られています: EBAD
。 C
はログに記録されるときは不明です。
説明:コンパイラーは、 setTimeout
およびgetDataFromDatabase
setTimeout
停止しません。彼が記録する最初の行はE
です。コールバック関数( setTimeout
最初の引数)は、設定されたタイムアウト後に非同期で実行されます。
詳細:
-
E
はsetTimeout
がありません -
B
タイムアウトは0ミリ秒です -
A
は1000ミリ秒のタイムアウトが設定されています -
D
はA
後に来るようにD
1000ミリ秒待機する必要がある後、データベースを要求しなければなりません。 - データベースのデータが要求されたときには不明であるため、
C
は不明です。それはA
前後にある可能性があります。
非同期エラー処理
キャッチしよう
エラーは常に処理する必要があります。同期プログラミングを使用している場合は、 try catch
使用できます。しかし、これは非同期で作業しても機能しません!例:
try {
setTimeout(function() {
throw new Error("I'm an uncaught error and will stop the server!");
}, 100);
}
catch (ex) {
console.error("This error will not be work in an asynchronous situation: " + ex);
}
非同期エラーは、コールバック関数内でのみ処理されます。
働く可能性
イベントハンドラ
Node.JSの最初のバージョンには、イベントハンドラがあります。
process.on("UncaughtException", function(err, data) {
if (err) {
// error handling
}
});
ドメイン
ドメイン内では、エラーはイベントエミッタを介して解放されます。これを使用することで、すべてのエラー、タイマー、暗黙的にドメイン内に登録されたコールバックメソッドのみが使用されます。エラーが発生すると、エラーイベントが送信され、アプリケーションがクラッシュしないようにします。
var domain = require("domain");
var d1 = domain.create();
var d2 = domain.create();
d1.run(function() {
d2.add(setTimeout(function() {
throw new Error("error on the timer of domain 2");
}, 0));
});
d1.on("error", function(err) {
console.log("error at domain 1: " + err);
});
d2.on("error", function(err) {
console.log("error at domain 2: " + err);
});
コールバック地獄
あまりにも多くのコールバック関数をコールバック関数の中にネストすると、コールバック・ヘル(ピラミッド・ドゥームまたはブーメラン・エフェクト)も発生します。次に、ファイルを読み込む例を示します(ES6)。
const fs = require('fs');
let filename = `${__dirname}/myfile.txt`;
fs.exists(filename, exists => {
if (exists) {
fs.stat(filename, (err, stats) => {
if (err) {
throw err;
}
if (stats.isFile()) {
fs.readFile(filename, null, (err, data) => {
if (err) {
throw err;
}
console.log(data);
});
}
else {
throw new Error("This location contains not a file");
}
});
}
else {
throw new Error("404: file not found");
}
});
「コールバック地獄」を避ける方法
2つ以上のコールバック関数をネストすることを推奨します。これはコードの可読性を維持するのに役立ち、今後も維持管理が容易になります。 2つ以上のコールバックをネストする必要がある場合は、代わりに分散イベントを使用してみてください。
asyncと呼ばれるライブラリもあり、npmでコールバックとその実行を管理するのに役立ちます。コールバックコードの可読性が向上し、コールバックコードフローを並列または直列で実行できるようにするなど、コールバックコードフローをより詳細に制御できます。
ネイティブ約束
プロミスは非同期プログラミングのツールです。 JavaScriptでは約束が彼らのために知られthen
方法。約束事には、「保留中」と「解決済み」の2つの主要な状態があります。約束が「解決」されると、「保留」に戻ることはできません。これは、約束事は一度しか起こらない出来事には大抵良いことを意味します。 「解決済み」状態には「解決済み」と「拒否」の2つの状態があります。 new
キーワードを使用して新しい約束事を作成し、関数をコンストラクタnew Promise(function (resolve, reject) {})
渡すことができます。
Promiseコンストラクタに渡された関数は、通常、それぞれresolve
とreject
という名前の第1と第2のパラメータを常に受け取ります。これらの2つのパラメータの命名は慣習的なものですが、約束された状態を「解決済み」状態または「拒否」状態のいずれかに入れます。これらのいずれかが呼び出されると、約束は「保留中」から「解決済み」になります。 resolve
は、非同期であることが多い、目的のアクションが実行され、アクションがエラーを起こした場合にreject
が使用されたときに呼び出されます。
以下のタイムアウトにはPromiseを返す関数があります。
function timeout (ms) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("It was resolved!");
}, ms)
});
}
timeout(1000).then(function (dataFromPromise) {
// logs "It was resolved!"
console.log(dataFromPromise);
})
console.log("waiting...");
コンソール出力
waiting...
// << pauses for one second>>
It was resolved!
timeoutが呼び出されると、Promiseコンストラクタに渡された関数は遅滞なく実行されます。次にsetTimeoutメソッドが実行され、そのコールバックが次のms
ミリ秒、この場合ms=1000
起動するように設定されます。 setTimeoutへのコールバックはまだ起動されていないので、timeout関数は呼び出し側スコープに制御を返します。 then
メソッドの連鎖は、Promiseが解決されたときに後で呼び出されるように格納されます。ここにcatch
メソッドがあれば、それも保存されますが、約束が「拒否」された場合には/が実行されます。
スクリプトは 'waiting ...'を表示します。 1秒後にsetTimeoutは解決関数を呼び出すコールバックを "It was resolved!"という文字列で呼び出します。その文字列は、 then
メソッドのコールバックに渡され、ユーザーに記録されます。
同じ意味で、コールバックを必要とする非同期のsetTimeout関数をラップすることができます。これは、任意の非同期アクションを約束で囲むことができます。
約束の詳細については、JavaScriptのドキュメントの約束をご覧ください。