備忘録的プログラミングリファレンス

非同期処理(コールバック関数)

 非同期処理とは、マルチに実行処理を進める方法のことです。

 プログラムの実行において、ファイルの読み書きやネットワーク経由の読み書き、決まった期間後に処理が開始するや一定時間ごとに処理を行うというように待ちになる処理があります。
 このような処理によって実行待ちにならないようにマルチに処理を進めたいものです。それを可能にするのが非同期処理です。

 非同期処理にはコールバック関数と呼ばれる手法を用います。コールバック関数の実行はシステムに任せます。

コールバック関数の定義
// コールバックに使用する関数
function func_a( value_ ){
	return( "渡された値は" + value_ );
}

function example_func( callback_func, val_a ){
	console.log( callback_func( val_a ) );
}

example_func( func_a, "a" );

 非同期処理が複数あると問題が発生することがあります。非同期処理の順番は基本的にシステム任せになってしまうのですが、複数の非同期処理を順次に処理したいときがあります。

 複数の処理を順次行うためにプロミスジェネレーターが存在しています。
 プロミスは、関数が成功した場合と失敗した場合で処理を分岐できます。
 ジェネレーターは、事前に処理の順番を指定することができます。

ページ内 Index

コールバック関数

 コールバック関数自体は非同期処理ではありませんが、非同期処理によく使われます。まずはコールバック関数について解説します。

コールバック関数の作成

 コールバック関数は、関数に処理してほしい関数を渡すというものです。関数の中で引数として渡された関数を実行します。

コールバックの定義
// コールバックに使用する関数
function func_a( value_ ){
	return( "渡された値は" + value_ );
}

function example_func( callback_func, val_a ){
	console.log( callback_func( val_a ) );
}

example_func( func_a, "a" );

 上記の例では example_func() の引数がコールバック関数になります。関数の中でコールバック関数を実行している部分があります。

 コールバック関数は呼び出し時に直接無名関数を定義することもできます。

コールバックを直接定義
function example_func( callback_func, val_a ){
	console.log( callback_func( val_a ) );
}

example_func(
	function( value_ ){
		return( "渡された値は" + value_ );
	},
	"b"
);

 非同期処理は実行処理とは別にさらに他の処理を作り出すものです。非同期処理においてコールバックはよく使われます。その代表例が setTimeout() です。

setTimeout()

 setTimeout() は一定時間後に処理を行う非同期処理で、よく非同期処理の解説に使われます。

 setTimeout() は第1引数で渡された関数を指定時間後に実行する特徴を活かして非同期処理の例に使われます。

 上記の例は5秒後に setTimeout() の第1引数が実行されます。処理は、 setTimeout() 以降に継続されます。

 setTimeout() は内部でエラーがあると放棄されることがあります。 しかし、 setTimeout() はエラーを返さないので非同期処理として監視することができない欠点があります。

 次は処理が成功したか失敗したかを任意に管理できるプロミス( Promise )という機能についてです。

プロミス( Promise )

 プロミス( Promise )は処理を管理する機能を与えます。プロミスによってコールバック関数が成功した場合と失敗した場合に振り分けることができます。
 プロミスには以下の状態(ステータス)があります。

状態(ステータス)概要
pending処理中。Promise の初期の状態
fulfilled成功。成功したことを伝える必要がある
rejected失敗。失敗したことを伝える必要がある

 pending は Promise のコールバック関数が処理中であることを示します。これはデフォルト値です。
 fulfilled は Promise のコールバック関数が成功したことを示します。rejected は Promise のコールバック関数が失敗したことを示します。どちらも Promise のコールバック関数の中で任意に伝える必要があります。

Promise のみを定義

 最初に Promise のみを定義してその動作を確認します。

 fulfilled、rejected は以下のように Promise() のロールバック関数の第1引数、第2引数として指定します。
 そして、任意の箇所に処理の成功と失敗を伝える _fulfilled()、_rejected() を設定します。(ここでは _rejected を引数で宣言していますが未だ以下のコードでは使っていません。)

 プロミスのコールバック処理が成功したことを伝えると、プロミスの then() メソッドが実行されます。失敗すると catch() メソッドが実行されます。

 Promise のコールバック関数の管理機能を利用して非同期処理を条件ごとに分けることができます。

関数の中で Promise を利用

 上記の例では Promise が自動で開始してしまいます。ある関数の中で Promise を使うようにします。そうすればその関数を呼び出せば Promise が実行されます。

 以下の例では、test_async() がある関数です。こうすれば Promise にパラメーターを渡すことができます。
 引数を渡すことができますので、引数が 1 のときは成功し、0 の時は失敗と条件分岐します。

 関数 test_async() の中で Promise が実行されます。関数を呼び出さない限り Promise は発生しません。

非同期処理と Promise

 今度は Promise の中に非同期処理を定義します。非同期処理は指定時間後に処理が発生する setTimeout() メソッドを利用します。

 非同期処理 setTimeout() メソッドが実行されたら Promise の fulfilled を呼び出します。

 処理の順では Promise が成功するほうが先ですが、非同期処理 setTimeout() によって処理結果が後になっており非同期処理が加えることができました。

 実際には成功か失敗かの条件を事前に決めません。非同期処理が正しく実行されたか/否かによって分岐すべきです。ファイル処理やネットワークを経由する場合はエラー処理があります。

 Promise では処理が成功したか失敗したかによって次の処理を分岐することができますが、事前に実行する関数の順番を決めるものではありません。
 事前に処理の順番を決める方法にジェネレーターを利用する方法があります。

ジェネレーター

 ジェネレーターで、呼び出す関数の順番を決めることができます。
 非同期処理のための関数を含める前にジュネレーターで関数が呼び出せるのかを確かめます。

ジェネレーター
function simple_function( attr_var ){
	console.log( attr_var );
}

function* example_Generator(){
	const val_a = yield simple_function("a");
	const val_b = yield simple_function( "b" );
	const val_c = yield simple_function( "c" );
	const val_end = yield simple_function( "end" );
}

 *が付いた関数がジェネレータの定義です。中のyieldステートメントによって処理順を登録します。

 呼び出しには以下のように next() を使用します。

 ここでは run_Generator() という関数を呼び出すことでジェネレーターを実行します。

ジェネレーターの実行
function run_Generator( gnrtr ){
	const iterator =  gnrtr();	// イテレーターに変換

	(function do_Iterate( val_ ){
			const crrnt_iterator = iterator.next();
			if( !crrnt_iterator.done ){
				do_Iterate();
			}
	})();
}

 以下は上記の例を反映した動作例です。

 次は Promise を加えます。以下の例では simple_function() に Promise() を加えます。
 Promise() の関数定義では 失敗した場合の _rejected がありますがここでは使いません。

 上記の例では、IIFE と呼ばれる即時実行の関数式を使用しています。

 Promise() を組み合わせました。Promise で成功を実行しないと次へとは進みません。このことで順次関数が実行されます。

 次に非同期処理を組み合わせの例を示すところですが、setTimeout() ではエラー処理がないために役不足です。
 ネットワーク経由のレスポンスかファイル処理での非同期処理を組み込んだ例を示すところですが、ここでは詳しく解説しません。是非調べてみてください。
 また、ファイル処理はブラウザで動く JavaScript ではその機能自体がありません。 Node.js のようにファイル操作ができる環境でなくてはできません。