
皆さん、こんにちはこんばんは。
今回は少しややこしいPromiseを使った再帰関数を作っていきたいと思います。
再帰関数だけならややこしいことはないんですが、再帰関数内でPromiseを使う場合って少しややこしく、混乱してしまうんですよね。
※私も実際にそうでした。
なので、サンプルコードを使いつつ解説していきたいと思います。
サンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
const checkStatus = async (retry) => { if(typeof retry === 'undefined') { retry = 0 } else { retry++ } return new Promise((resolve, reject) => { if(retry > 100) { reject() return } getStatus().then((status) => { if(status === '求めている状態') { resolve(status) } else { checkStatus(retry).then(resolve).catch(reject) } }).catch(reject) }) } const getStatus = async () => { return new Promise(async (resolve) => { await wait(1000) const random = Math.random() const status = random > 0.5 ? '求めている状態' : '求めていない状態' resolve(status) }) } const wait = async (mtime) => { return new Promise((resolve) => { setTimeout(() => { resolve() }, mtime) }) } await checkStatus() |
まず、全体の構成として、”checkStatus”関数が呼び出されたタイミングで”status”という変数の状態が”求めている状態”になるまで、”checkStatus”関数を呼び出すような仕組みとなっています。
※再帰関数なので、念の為バカ避けも入れてます。
コードを詳しく見ていきます
それでは、サンプルコードを使って実際にPromiseを活用した再帰関数ではどのような動きになっているのか見ていきましょう。
checkStatus関数
サンプルコードではこの”checkStatus”関数が再帰関数として扱われていますね。
1 2 3 4 5 6 |
if(typeof retry === 'undefined') { retry = 0 } else { retry++ } } |
こちらはほとんど言うまででもないですが、再帰関数も条件によっては無限ループに陥りやすいので、バカ避けとして実行された回数をチェックしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
return new Promise((resolve, reject) => { if(retry > 100) { reject() return } getStatus().then((status) => { if(status === '求めている状態') { resolve(status) } else { checkStatus(retry).then(resolve).catch(reject) } }).catch(reject) }) |
そしてここですね。ここが一番のメインとなる部分ですね。
“getStatus”という関数がPromiseオブジェクトを返却するので、thenメソッドのコールバックを利用して、”status”という条件をif文で確認しています。
まず、if文の条件に合ったstatusが返却された場合、そこで処理を停止することを想定しているのでresolveメソッドで処理が成功したことをコールしてあげます。
で、ややこしくなるのがここからなんですが、もし”status”が期待した状態でなければ、期待する状態になるまで再帰することとなります。
同期関数であればそのまま関数を呼び出すだけで良いんですが、Promiseの場合はそうは行きません。
“checkStatus”を呼び出しますが、thenメソッドにresolveメソッドをコールバックの関数として渡してあげます。
そうすることで再帰関数として呼び出された”checkStatus”関数がresolveをコールすると再帰関数として呼び出した”checkStatus”のthenメソッドが連鎖して”checkStatus”関数の処理を終了するような流れとなります。
もし、”checkStatus”関数の戻り値を取得した場合は、このようにコードを変更します。
1 2 3 4 5 6 |
const status = await checkStatus().then((status) => { return status }).catch((e) => { return false }) console.log(status) |
こうすることで”status”を取得することができます。
最後に
こうしてみると、コールバック地獄に陥りそうなんですが、Promiseをうまく活用することでコールバック地獄にもならず、うまいこと再帰的に呼び出すことができます。
再帰関数は使い所も多々あると思いますし、Promiseはかなり便利なものなので活用する機会があれば是非試してみてください。