[フロントエンド] 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をフォローして貰えたら嬉しいです ^ ^
最後までご覧頂きましてありがとうございました!






