PythonからJavascriptの非同期を学んだ時のまとめ。

公開:2020/11/17
更新:2020/11/17
8 min読了の目安(約7300字TECH技術記事

最初に

Javascriptの非同期をやっていく上でこれがすごい大事な気がする。

JSは基本的に非同期処理がデフォルトで作成された言語

そのためこれから使うcallback関数、Promis、await asyncでは関数を非同期に変換するというより、非同期に待機処理を追加して処理を上手くコントロールするという使い方になる。

他の言語ではこれが逆になるから、少し混乱するのかもしれない。

記事の投稿目的
現在無職でJSについて勉強しています。しかし、自分の解釈で合っているか不安のため、 Notionで勉強用にまとめた文章を少し誰かに説明するつもりで編集して載せています。

コメント欄でここもう少し詳しく、これについては理解しているの説明できるなど指摘して下さると勉強の糧になり助かります。

コールバック関数を使った非同期

Javascriptでのcallback 非同期

function adding(callback, num){
	console.log(num);//最初は0
	callback(num);//wait(num=>{n++; ....},num};ここでnumをインクリメント
}

//callback()と引数を忘れるとNan(Not a number)が表示される。

//waitのコールバック関数の中でさらにwaitを呼んでそれを繰り返す。
adding(num => {
	num++;
	adding(num => {
		num++;
		adding(num => {
				num++;

		}, num);
	}, num);
}, 0);

//実行結果
0
1
2

Pythonでのcallback 同期

num = 0

def say(callback, num):
	print(num)
	return callback(num)

#無名関数を使用してないので関数が2つになる。
def adding(num):
	num += 1
	return num

num = say(adding, num)
num = say(adding, num)
num = say(adding, num)

#実行結果
0
1
2

Pythonの場合は say で呼ばれた addingreturn が実行されるまで次の処理にはいかないが、JSの場合は待たずに次の処理に行ってしまう。上記のコード例ではあまり違いを提示出来てなくて申し訳ないが、言語として根本的に仕様が違う。JSは非同期でPythonは同期処理そのためJSコードをPythonに変換してコードを記述する際は時折処理を待機させる必要が出てくる。

そのためJSでfetch投げたりしても、レスポンス待たずに次の処理に移行する。

例 ※実際には動作しない。

//サーバ取得
const res = getDataFromServer();//レスポンス待たずに次の処理へ

//取得したデータ加工
res.doDomething();//ここでエラーが発生する。

//全く関係ない他の処理
doSomethingElse();

コールバック関数を用いた非同期を実行する。

function wait(callback, num){
	//setTimeoutではアロー関数(無名関数)で引数がない別の関数を
	//コールバックとして取る。その中でwaitがcallbackとして受け取った無名関数を処理する。
	//0.1秒後に実行される。
	setTimeout(() => {
		//1秒後に出力される。
		console.log(num);
		callback();
	}, 1000);
	console.log(num);//先に出力される。
}

wait(() => {
	//無名関数をwaitのcallbackにする。
	console.log('callback function is called');
//0をnumの引数として取る。
}, 0);

//実行結果
0
0
callback function is called

コールバックチェーンにしてみる。これは地獄らしい。

function wait(callback, num){
	setTimeout(() => {
		console.log(num);//最初は0
		callback(num);//wait(num=>{n++; ....},num};ここでnumをインクリメント
	}, 100);
}

//callback()と引数を忘れるとNan(Not a number)が表示される。

//waitのコールバック関数の中でさらにwaitを呼んでそれを繰り返す。
wait(num => {
	num++;
	wait(num => {
		num++;
		wait(num => {
				num++;

		}, num);
	}, num);
}, 0);

//最初は一番外側に書かれた処理が行われるので最初の値は0になる。

一行にしてみた。外側から内側に処理が向かっていく感じ

wait(num=>{num++;wait(num=>{num++; wait(num=>{num++;},num); },num);},0);//num 1
wait(num=>{num++; wait(num=>{num++;},num);} ,1);//num 2
wait(num=>{num++;} ,2);//num 3

Promiseを使って処理を待機させる。

上記のコードをpromiseを使って書き換えていく。

function wait(num){
	//引数なしの無名関数をアロー関数で書いてる。
	//と思わせてPromiseではresolve, rejectを引数として取る。
	return new Promise((resolve, reject) => {
		//このアロー関数の中で非同期処理を書いていく。
		setTimeout(() => {
			console.log(num);//最初は0
			//ここが呼ばれた時点で次の処理に移る。callbackと同じ機能エラーを出す時はrejectで呼び出す??
			resolve(num);//wait(num=>{n++; ....},num};ここでnumをインクリメント
		}, num);
	});
}

//waitの処理が終わった後の処理はthenで行う。
//resolveで渡した引数がthenメソッドのコールバック関数の引数となる。
wait(0).then(num => {
	num++;
	//thenメソッドの中の関数の戻り値にPromiseを渡す事でこの処理も非同期で行われる。
	return wait(num);
})

同期処理のように処理が終了してから別の処理を行なわせたい(チェーンする)場合は then を使用する。動画では非同期処理を繋げるという表現だったが、それは同期処理ではないのか?と思う。

function wait(num){
	//引数なしの無名関数をアロー関数で書いてる。
	//と思わせてPromiseではresolve, rejectを引数として取る。
	return new Promise((resolve, reject) => {
		//このアロー関数の中で非同期処理を書いていく。
		setTimeout(() => {
			console.log(num);//最初は0
			//ここが呼ばれた時点で次の処理に移る。callbackと同じ機能エラーを出す時はrejectで呼び出す??
			resolve(num);//wait(num=>{n++; ....},num};ここでnumをインクリメント
		}, 100);
	});
}

//waitの処理が終わった後の処理はthenで行う。
//resolveで渡した引数がthenメソッドのコールバック関数の引数となる。
wait(0).then(num => {
	num++;
	//thenメソッドの中の関数の戻り値にPromiseを渡す事でこの処理も非同期で行われる。
	//戻り値は次のthenメソッドの引数として渡される。
	// returnで返さずにwait(num)と記述するとチェーンが切れ非同期になる。
	return wait(num);
}).then(num => {
		num++;
		return wait(num);
}).then(num => {
		num++;
		return wait(num);
}).then(num => {
		num++;
		return wait(num);
}).then(num => {
		num++;
		return wait(num);
})

//実行結果
0
1
2
3
4

今度はrejectを使用してエラーをハンドリングしていく。

function wait(num){
	//引数なしの無名関数をアロー関数で書いてる。
	//と思わせてPromiseではresolve, rejectを引数として取る。
	return new Promise((resolve, reject) => {
		//このアロー関数の中で非同期処理を書いていく。
		setTimeout(() => {
			console.log(num);//最初は0
			if(num === 2){
				reject(num);
			}else{
				//ここが呼ばれた時点で次の処理に移る。callbackと同じ機能エラーを出す時はrejectで呼び出す??
				resolve(num);//wait(num=>{n++; ....},num};ここでnumをインクリメント
			}
			
		}, 100);
	});
}

//waitの処理が終わった後の処理はthenで行う。
//resolveで渡した引数がthenメソッドのコールバック関数の引数となる。
wait(0).then(num => {
	num++;
	//thenメソッドの中の関数の戻り値にPromiseを渡す事でこの処理も非同期で行われる。
	//戻り値は次のthenメソッドの引数として渡される。
	// returnで返さずにwait(num)と記述するとチェーンが切れ非同期になる。
	return wait(num);
}).then(num => {
		num++;
		return wait(num);
}).then(num => {
		num++;
		return wait(num);
}).then(num => {
		num++;
		return wait(num);
}).then(num => {
		num++;
		return wait(num);
}).catch(num => {
		num++;
		console.error(num, 'error');
});

//実行結果
0
1
2
3 error

非同期で処理をたくさん走らせて全ての並列処理が終わったタイミングで何かする場合は Promise.all を使用する。

//この関数を非同期で並列に走らせる。
function wait(num){
	//引数なしの無名関数をアロー関数で書いてる。
	//と思わせてPromiseではresolve, rejectを引数として取る。
	return new Promise((resolve, reject) => {
		//このアロー関数の中で非同期処理を書いていく。
		setTimeout(() => {
			console.log(num);//最初は0
			if(num === 2){
				reject(num);
			}else{
				//ここが呼ばれた時点で次の処理に移る。callbackと同じ機能エラーを出す時はrejectで呼び出す??
				resolve(num);//wait(num=>{n++; ....},num};ここでnumをインクリメント
			}
			
		}, num);
	});
}

Promise.all([wait(1000), wait(1500), wait(2000)]).then(nums => {
	console.log(nums)
})

//実行結果
1000
1500
2000
[1000, 1500, 2000]
//全ての処理が実行されたのちに配列が返ってくる。

race を使って 一つの処理が終わったタイミングで then を呼ぶ事ができる。

Promise.race([wait(1000), wait(1500), wait(2000)]).then(nums => {
	console.log(nums + 1);
})
//実行結果
1000
1001 //ここでthenが呼び出された。numsは一つしか値がないので配列になっていない。
1500
2000

await asyncを使った非同期

ルール

  1. awaitを付けると戻り値が返るまで待機する。
  2. awaitを使用した関数の先頭にasyncを付ける。この関数は非同期であると示す。
async function sample() {
	//awaitを付ける事でasyncFn()の戻り値が来るまでnum++は実行されない。
	
	const num = await asyncFn();
	num++;
	return num;
}

//上記のコードをプロミスで記述する場合
//asyncFn()が実行された時点でthenメソッドが呼ばれる。
asyncFn(0).then(num => {
	num++;
	return num;
})

先ほど使用したPromiseの関数async awaitで非同期にする。


function wait(num){
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(num);
			if(num === 2){
				reject(num);
			}else{
				resolve(num);
			}
		}, 100);
	});
};

async function init(){
	let num = 0
	try{
		num = await wait(num);
		num++;
		num = await wait(num);
		num++:

	}catch(e){
		throw new Error('Error is occured', e);

	}
	return num;//Promiseでラップされた値が返る。
}

init();//戻り値がPromiseなのでそのままthenメソッドが使用できる。

参照

下記の動画を学習しながら、疑問に思った事をまとめて記事にしました。この方udemyで講師をしている方で動画がとても丁寧で分かりやすい(しかも無料!!)のでJavascriptで非同期を学ぶなら絶対おすすめです。

【JavaScript】非同期操作について学ぼう1(コールバック関数)

【JavaScript】非同期操作について学ぼう2(Promise関数)

【JavaScript】非同期操作について学ぼう3(Await/Async関数)

記事に関するコメント等は

🕊:Twitter
📺:Youtube
📸:Instagram
👨🏻‍💻:Github
😥:Stackoverflow

でも受け付けています。どこかにはいます。