[フロントエンド] ES7のasync/awaitを使って、Promiseを同期的に処理する
こんにちは、@yoheiMuneです。
JavaScriptといえば非同期プログラミングですが、async/await構文を使うと、同期的に処理を実行できるようになります。今日は、そんなとても便利なasync/awaitについてブログに書きたいと思います。
Async/Awaitと同等の機能は、
今日はそんな便利な
そして、
- ES async/awaitを全力で使ってみて発見したイディオム - Qiita
- async function - JavaScript | MDN
- Understanding JavaScript’s async await
- ES.next async/await + Promise で解決できる事
- Promiseとasync-awaitの例外処理を完全に理解しよう - Qiita
本ブログでは、フロントエンド、Go言語、Python、Linux、Node.js、インフラ、Swift、Java、機械学習、などの技術トピックを発信をしていきます。「プログラミングで困ったその時に、解決の糸口を見つけられる」そんな目標でブログを書き続けています。今後も役立つネタを書いていきますので、ぜひ本ブログのRSSやTwitterをフォローして貰えたら嬉しいです ^ ^
最後までご覧頂きましてありがとうございました!
JavaScriptといえば非同期プログラミングですが、async/await構文を使うと、同期的に処理を実行できるようになります。今日は、そんなとても便利なasync/awaitについてブログに書きたいと思います。
目次
Async/Awaitとは
ECMAScriptで定義されている(2017/10現在はディスカッション中)、新しいJavaScriptの構文です。これを使うことで、Promiseベースの処理を同期的に処理することが可能になります。例えば以下のようなコードを書くことができます。async function main () { // Promiseベースの処理を、同期的に処理できる. const val1 = await sleep(100, 'Hello') console.log('val1:', val1) // val1: Hello } function sleep (msec, val) { return new Promise((resolve, reject) => { setTimeout(resolve, msec, val) }) }Promiseベースの処理を同期的に実装できるのは、非常に素晴らしいですよね。
Async/Awaitと同等の機能は、
generator
とyield
を用いて実現できますが、それを言語レベルでサポートしています(専門的にいうとAsync/Awaitはgeneretaor/yield
の糖衣構文(Syntax sugar)です)。今日はそんな便利な
async
とawait
の使い方を見てみたいと思います。async/awaitの使い方
いくつかのユースケースに分けて、使い方を見てみたいと思います。その前に
最初の例でも使いましたが、これ以降のサンプルで、以下のPromiseを返す関数を例に、説明していきます。// 指定秒数待った後に、引数で指定されたvalをresolveで返却します. function sleep (msec, val) { return new Promise((resolve, reject) => { setTimeout(resolve, msec, val) }) }
基本的な使い方
基本的な使い方は、async
を付与した関数を定義し、その中でawait
を利用します。そうすることで、Promiseを同期的に扱うことができます。// awaitを使いたい場合には、必ずasyncを付与する. async function main () { // awaitで呼び出すことで、同期的に処理できる. const val1 = await sleep(100, 'Hello') console.log('val1:', val1) return 'OK' }大切なことは、
async
関数の中でawait
を使うことです。async
関数以外でawait
を記述すると、構文エラーになります。そして、
async
が付与された関数はPromiseを返却します。main
関数の終了はthen
関数で補足することができます。main().then(result => { console.log('result:', result) // result: OK })このように、
async
とawait
を用いると、Promiseチェーンを実装しなくても、非同期処理が実装できてすごく素敵です。複数のawaitを直列で呼び出す
以下のように行を変えて、複数のawaitを書くと、それぞれの処理を直列で実行します。// Sequencial. const t1 = Date.now() const val2 = await sleep(500, 1) const val3 = await sleep(500, 2) const val4 = await sleep(500, 3) console.log(`val2+val3+val4=${val2 + val3 + val4}`) // val2+val3+val4=6 console.log(`${Date.now() - t1}ms`) // 1,500ms // 1,500ms <= 500msずつ3回、直列で呼び出されている500ms待機する処理を3つ実行しているので、1500msかかっていることがわかります。
複数のawaitを並列で呼び出す
複数のawaitを並列に呼び出すこともできます。並列で呼び出すには、以下のように演算式の中でawait
をするか、
// 並列にawaitを処理する const t2 = Date.now() const val5 = sleep(500, 1) const val6 = sleep(500, 2) const val7 = sleep(500, 3) const val8 = await val5 + await val6 + await val7 console.log(`val5+val6+val7=${val8}`) // val5+val6+val7=6 console.log(`${Date.now() - t2}ms`) // 500ms // 500ms <= 500msの処理3つが、並列で実行された以下のように、
Promise.all
を用いることで実現できます。// 並列にawaitを処理する const t3 = Date.now() const val9 = sleep(500, 1) const val10 = sleep(500, 2) const val11 = sleep(500, 3) // Promise.allで並列に処理する. let val12 = await Promise.all([val9, val10, val11]) // 戻り値は、配列で返ってくる. console.log('val12:', val12) // val12: [ 1, 2, 3 ] // 値を合計して val12 = val12.reduce((sum, val) => sum + val, 0) // 表示する console.log(`val9+val10+val11=${val12}`) // val9+val10+val11=6 console.log(`${Date.now() - t3}ms`) // 500ms // 500ms <= 500msの処理3つが、並列で実行されたこのように並列でPromiseを処理することもできます。Webからリソースを取得する場合などに、並列処理ができると素敵ですね。
エラーを処理する
プロダクションで用いる場合には、エラー処理もちゃんと行う必要があります。ここではいくつかのユースケースを元に、どのようにエラーをハンドリングするかを説明します。async関数内でエラーが発生する場合
まずは、async
関数内でエラーが発生する場合です。例えば以下のように例外が送出されるとします。async function main2 () { throw new Error('MAIN2 ERROR!!!') }このエラーをハンドリングするには、
catch
メソッドで対応します(Promiseのエラー処理と同じですね)。// catchを用いてエラーを処理する. main2().catch(err => { console.log('main2:err:', err) }) // main2:err: Error: MAIN2 ERROR!!! // at main2 (/Users/munesadayohei/git/frontend-playground/020-async-await/sample.js:64:11) // at Object.<anonymous> (/Users/munesadayohei/git/frontend-playground/020-async-await/sample.js:59:1) // at Module._compile (module.js:569:30) // at Object.Module._extensions..js (module.js:580:10) // at Module.load (module.js:503:32) // at tryModuleLoad (module.js:466:12) // at Function.Module._load (module.js:458:3) // at Function.Module.runMain (module.js:605:10) // at startup (bootstrap_node.js:158:16) // at bootstrap_node.js:575:3このように、
async
関数内でのエラーは、Promiseベースで処理していきます。await対象の処理内でエラーが発生する場合
続いて、await対象のPromiseでエラーが発生する場合です。この場合はawait
で対象を同期的に処理しているため、Promise風にエラー処理できません。代わりにtry/catch
構文でエラーをハンドリングします。// エラーを送出する関数 function rejectFunction () { return new Promise((resolve, reject) => { throw new Error('REJECT FUNCTION ERROR!!!') // rejectした場合にも、同様の扱いになります. // reject(new Error('REJECT FUNCTION ERROR!!!')) }) } async function main3 () { try { await rejectFunction() } catch (e) { console.log('catched: ', e) } return 'main3:result: finish' } // catched: Error: REJECT FUNCTION ERROR!!! // at Promise (/Users/munesadayohei/git/frontend-playground/020-async-await/sample.js:98:15) // at Promise (<anonymous>) // at rejectFunction (/Users/munesadayohei/git/frontend-playground/020-async-await/sample.js:97:12) // at main3 (/Users/munesadayohei/git/frontend-playground/020-async-await/sample.js:120:15) // at Object.<anonymous> (/Users/munesadayohei/git/frontend-playground/020-async-await/sample.js:114:1) // at Module._compile (module.js:569:30) // at Object.Module._extensions..js (module.js:580:10) // at Module.load (module.js:503:32) // at tryModuleLoad (module.js:466:12) // at Function.Module._load (module.js:458:3) // main3:result: finishこのように、
try/catch
構文でエラーハンドリングができるようになります。コールバックベースやPromiseベースよりもシンプルなので良いなと思います。参考資料
async/awaitを学ぶために、以下の資料を参照しました。ありがとうございます。- ES async/awaitを全力で使ってみて発見したイディオム - Qiita
- async function - JavaScript | MDN
- Understanding JavaScript’s async await
- ES.next async/await + Promise で解決できる事
- Promiseとasync-awaitの例外処理を完全に理解しよう - Qiita
最後に
async/await構文は便利ですね。やろうやろうと思って数ヶ月放置してましたが、早くやれば良かった。ドシドシと使っていきたいと思います。あと、久しぶりのフロントエンドの記事楽しかった〜。今後も色々とアウトプットしていきたいと思います。本ブログでは、フロントエンド、Go言語、Python、Linux、Node.js、インフラ、Swift、Java、機械学習、などの技術トピックを発信をしていきます。「プログラミングで困ったその時に、解決の糸口を見つけられる」そんな目標でブログを書き続けています。今後も役立つネタを書いていきますので、ぜひ本ブログのRSSやTwitterをフォローして貰えたら嬉しいです ^ ^
最後までご覧頂きましてありがとうございました!