
皆さん、こんにちはこんばんは。
よくajaxやfetch、setTimeoutのような非同期関数を使っているとコールバック地獄に陥った経験がある方が多いのではないでしょうか。
コールバック地獄を回避する小技は様々ですが、今回は非同期処理を行うPromiseで複数のPromise処理を指定した順番に実行していきコールバック地獄も防げるしくみを紹介していきたいと思います。
※Promiseに関する詳細は省きます。
Promiseの複数処理
Promiseを複数処理する場合、実はPromiseオブジェクトに”all”という静的メソッドが存在しています。
これは、配列でPromiseオブジェクトを引数に渡してあげると順番に実行し成功したら次の処理、成功したら次の処理と引数に渡した分だけ順番に実行してくれます。
すべてのPromise処理が完了したら配列ですべての結果が返ってくるというような流れとなっています。
Promise.all()でも仕組みとしては間違っていませんが、複数分の処理を一括で受け取るのではなく1つ1つ値を受け取って、値の結果により処理を振り分けれるようなロジックを紹介していこうと思います。
必要なクラスファイルを作成する
要件として以下の構成を作ります。
・タスクを処理するクラス
┗・実行タスク1
・実行タスク2
・実行タスク3
まずは”タスクを処理するクラス”というクラスを作成します。
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 |
class request { constructor() { this.chunks = [] this.requested = 0 } set(chunks) { if(typeof chunks !== 'object') { console.error('typeof chunks does not object') return } this.chunks = chunks return this } async run(callback, errorfn, res) { const len = this.chunks.length if(len) { if(typeof this.chunks[this.requested] !== 'undefined') { const res = await this.chunks[this.requested].execute().then((res) => { return res }).catch((error) => { console.error(error) return {error: true, result: false, message: 'システムエラー'} }) if(res.result && !res.error) { this.requested++ await this.request(callback, errorfn, res) } else { this.requested = 0 errorfn(res) } } else { this.requested = 0 callback(res) } } } } |
chunkという変数には実行するタスクオブジェクトが配列で追加されることを想定しています。
続いてAPIの処理を行うタスククラスを作成します。
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 |
class chunk { constructor(config) { const d_config = { url: '', options: { headers: { 'Content-Type': 'application/json' }, method: '', body: JSON.stringify({}) }, before_callback: () => {}, callback: () => {} }; this.config = Object.assign(d_config, config) } async execute() { return new Promise(async (resolve, reject) => { if(typeof this.config.before_callback === 'function') { this.config.before_callback() } const res = await fetch(this.config.url, this.config.options).then((res) => { return res.json() }).catch((e) => { reject({error: true, message: e}) }) if(typeof this.config.callback === 'function') { this.config.callback(res) } resolve(res) }) } } |
こちらがタスクになるクラスファイルです。
※APIリクエストにはfetchを使います。
これでAPI処理を順番に一括処理するクラスファイルが作成できました。
大まかな処理の内容としては以下です。
request
cunkクラスを順番に実行し、結果が成功すれば次のchunkクラスの処理を実行しますが、失敗した場合その時点で処理が中断されエラーコールバックが実行されるようになっています。
chunk
実際にAPIリクエストを処理するクラスです。
実行が成功や失敗した場合、requestに通知を与えるようになっています。
実際に使う方法は以下のようになります。
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 |
const req = new request(); req.set([ new chunk({ url: '/path/to/chunk1', options: { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: $('#value').val(), ymd: $('#dateformat').val() }) }, before_callback: () => { console.log('ファイルをチェックしています。'); } }), new chunk({ url: '/path/to/chunk2', options: { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ymd: $('#dateformat').val(); }) }, before_callback: () => { console.log('データを処理しています。'); } }) ]).run((res) => { console.log('すべての処理が完了しました。'); }, (e) => { console.log('処理に失敗しました。'); }); |
活用例ではかなり簡素にコーディングしていますが、独自にオプションを設定することでもっと複雑な処理を行ったり、
処理を分岐させるようなこともできそうですね!
活用場面
私がこのしくみをよく使う場面としてはファイルのアップロードや処理の進捗具合の%表示を行う時などに活用しています。
例えば、順番にAPIリクエストを実行する場面でローディングを表示させていたとしたら、今はどんな処理をしているのかというメッセージ表示もしやすくなるのでユーザーにとっても不愉快にならずに待機させることができます。
今回はfetchを使ったAPIリクエストでしたが、処理の内容を変えて結果を適切に返せることができれば色々な場面でも活用することができ、ユーザーの離脱防止に繋がると思いますので皆さんも是非試してみてください。