こんにちは、サーバーサイドエンジニアの菅原です。
今回はJavaScriptの非同期処理について今更ながら学んでみました。昔ながらのCallback、ES6から追加されたPromise、ES7から追加されたasync await、さらにはRxJsについても調べてみました。
背景
昨今はUXの需要の高まりから非同期処理を書くことが多くなり、リアルタイム性やチャット機能、パフォーマンス改善のためにも非同期処理のコードを書くことが増えています。ただ非同期処理のJavaScriptのコードは処理が増えるごとにコールバック地獄と呼ばれる可読性が損なわれる危険性があることも事実です。
今回は以下のプログラミング条件から非同期処理について簡単なコードを用いての紹介と補足でRxJsを用いた非同期処理を紹介していきます。
実行プログラムの条件
1, 認証ユーザかチェックを行う。
2, 1が成功するとウエパちゃんを表示させる
3, 1,2を順に実行する。
ウエパちゃん
ウエディングパーク公式キャラクターです。
ウエディングパークのサイトなどでも活躍しています。
Callbackを用いた非同期処理
これは非同期処理でも基本的なCallbackをしているJavaScriptコードです。
①②ともにsetTimeoutの中でcbメソッドを使いCallbackしています。
③の実行ではcheckAuthの関数を呼び出した中にネストしてfetchWpの関数を呼び出しています。
以下のコード量の時は特に可読性が悪くはないですが、追加する関数が増えれば増えるほどネストが深くなってしまい、可読性がなくなり冗長なソースになってしまいます。
// ①認証ユーザかチェックを行う。 const checkAuth = cb => { console.log('Checking Auth...') setTimeout(() => { cb(true); }, 2000); }; // ②ウエパちゃんを表示させる const fetchWp = cb => { console.log('Fetching WP...') setTimeout(() => { cb({ name: "ウエパちゃん" }); }, 2000); }; // ③実行 checkAuth(auth => { if (auth) { fetchWp(wp => { console.log(wp.name) }); } });
Promiseを用いた非同期処理 (ES6)
ES2015(ES6)から追加仕様として加わったPromise。
Promiseは3つの状態(resolve, reject, done)を持ちます。
①②の関数をPromiseでオブジェクトにラップしておきます。
③の実行時にthenというチェーンメソッドでその後の処理を記述します。
続けて処理を書く場合もメソッドチェーンでつなげていくことでネストが深くならずにコードがすっきりするでしょう
// ①認証ユーザかチェックを行う。 const checkAuth = () => { return new Promise((resolve, reject) => { console.log('Checking Auth...'); setTimeout(() => { resolve(true); }, 2000); }); }; // ②ウエパちゃんを表示させる const fetchWp = () => { return new Promise((resolve, reject) => { console.log('Fetching Wp...'); setTimeout(() => { resolve({ name: "ウエパちゃん" }); }, 2000); }); }; // ③実行 checkAuth() .then( isAuth => { if (isAuth) { return fetchWp() } } ) .then( wp => { console.log(wp.name) } )
async awaitを用いた非同期処理 (ES7)
ES2016(ES7)から追加仕様として加わったasync await Promiseと共存して使用できます。
①②でPromiseでラップし、③の実行時にasyncの中でawaitで関数を呼び出しています。
awaitはasyncで定義された関数内にしか使えません。また通常のコールバックやNodeコールバックは共用できません。awaitは関数の実行が終わるまで待機します。またPromiseでラップしている箇所をasync関数で定義することも可能です。その場合async関数が暗黙的にPromiseを返し、関数の戻り値がPromiseの処理完了の値になります。
// ①認証ユーザかチェックを行う。 const checkAuth = () => { return new Promise((resolve, reject) => { console.log('Checking Auth...'); setTimeout(() => { resolve(true) }, 2000) }) } // ②ウエパちゃんを表示させる const fetchWp = () => { return new Promise((resolve, reject) => { console.log('Fetching Wp...'); setTimeout(() => { resolve({ name: "ウエパちゃん" }); }, 2000) }) } // ③実行 (async function(){ const isAuth = await checkAuth() let user = null; if (isAuth) { wp = await fetchWp() } console.log(wp.name); })();
RxJsを用いた非同期処理 (補足)
補足にRxJs(Reactive Extensions)を用いてコードを書いています。「Observerパターン」でイベントやデータをストリームとして扱えることからプログラミングの可読性を向上させています。開発元はMicrosoftであり、ライブラリとしての信頼性もあります。コードではObservable.createでデータを作りObservableをハンドリングし、最後にsubscribeで出力しています。
// ①認証ユーザかチェックを行う。 const checkAuth = () => { return Rx.Observable.create(observer => { console.log('Checking Auth...') setTimeout(() => { observer.next(true); }, 2000); }) }; // ②ウエパちゃんを表示させる const fetchWp = () => { return Rx.Observable.create(observer => { cosole.log('Fetching Wp...'); setTimeout(() => { observer.next({name: 'ウエパちゃん'}); }, 2000); }) }; // ③実行 Rx.Observable.of(true) .switchMap(event => checkAuth()) .switchMap(isAuth => { if (isAuth) { return fetchWp() } }) .subscribe(wp => { console.log(wp.name); })
まとめ
いかがでしたでしょうか?非同期処理だけで様々な書き方ができます。
個人的にはES7のasync awaitが今後主流になっていくのではと思いましたが、Promiseが導入されてから可読性が一気に上がったのでPromiseでも問題ないと思いました。特にES7を導入していないサイトなどもあるのでPromiseで賄っていけるのではないかと思います。またトランスパイラを導入していなくてもjQueryのDefferd+Promiseである程度カバーできると思いました。
まだまだ熱い非同期処理界隈ですが皆さんもぜひ試してみてください。
Wedding Parkでは一緒に技術のウエディングパークを創っていくエンジニアを募集しています。
興味のある方はぜひ一度気軽にオフィスに遊びにきてください。