async/await は ES7 の機能で、非同期処理を記述する上で非常に便利な機能である(仕様は安定していないと思う) まだ実装している処理系はないと思うが、babel などの transpiler をつかうと利用できる
async/await をつかうと非同期処理を以下のように書くことができる
function a() { return new Promise(function(resolve, reject) { setTimeout(function() { resolve('hello, ') }, 0) }) } async function b() { var value = await a() return value + 'world' } async function c() { var value = await b() console.log(value) return 'this is async world' } c().then(function(val) { console.log(val) })
上のコードを babel-node などで実行すると hallo, world
に続いて this is async world
が表示される。
async function
は Promise を返し、 await
キーワードにより Promise が解決されるのを待つ(かのように見せる)ことができるので、async/await をつかうと boilerplate なコードを減らして非同期処理を扱うことができる。
TypeScript 実装での async/await の動作
現在の TypeScript の実装から await の動作をも少し詳しく見てみる(仕様は確認していない。。) 上のコードを TypeScript 1.6 で --target ES6 --experimentalAsyncFunctions
オプションをつけてコンパイルすると、以下のようなコードが出力される
// runtime var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) { return new Promise(function (resolve, reject) { generator = generator.call(thisArg, _arguments); function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); } function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } } function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } } function step(verb, value) { var result = generator[verb](value); result.done ? resolve(result.value) : cast(result.value).then(onfulfill, onreject); } step("next", void 0); }); }; // 上の async function b はこのように展開される function b() { return __awaiter(this, void 0, Promise, function* () { // --- (2) var val = yield a(); return val + 'world'; }); }
async function
は必ず Promise オブジェクトを返す。その Promise オブジェクトはasync function
が return した場合、その値で resolve するasync function
の中で throw されると、その値で reject する(例外は catch される)- 特別な場合として、一度も
await
されてない状態で例外が投げられると、その例外は補足されることなくそのまま throw される --- (※)
- 特別な場合として、一度も
await
は何らかの値をとり、その値が Promise でなければ、そのままその値を返す- 正確には、Promise でなければ Promise.resolve でラップされる
await
に渡した値が Promiseであれば
となっている。(余談だが、__awaiter
のコードは GitHub - tj/co: The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc) とほぼ同様である)
async/await のエラー処理
async/await をつかったときのエラーの流れを見てみる。以下のコードは
function a() { return new Promise(resolve) { setTimeout(function() { resolve('hoge') }, 0) } } async function b() { console.log(await a()) throw 'hello, ' } async function c() { try { await b() // --- (1) } catch(e) { // --- (2) throw e + 'error' } } async function d() { var val = await c() console.log("control don't reach here") } d().catch(function(val) { console.log(val) }) // --- (3)
- a で発生したエラーは async function 自体が捕まえ、自身が返す Promise を reject する
- (1) の await は Promise が reject したのを受けて、例外を投げるが、すぐに (2) で補足される
- エラー内容を更新して再度例外を投げるが、b が async function であるためやはり補足され、b が返す Promise を reject する
- c でまた同様に、 catch -> reject -> throw -> catch が行われ、(3) で出力される
となる。
内部的には Promise のエラー処理が走っているのだが、async function より先のコードでは .then
.catch
はいっさい出てこない。
エラー処理の書き方が同期プログラミングに置ける try ~ catch と同様になるので、Promise のエラー処理に手こずった人でも問題なくなっている
まとめ
ES7 async/await でのエラー処理(throw -> catch -> reject -> throw -> ...)の流れを追った。このフローは try ~ catch が多発してあまりパフォーマンスが欲はなさそうだけど、実際に ES7 として実装されるときはもっと効率的なものになるかもしれない(ので今 TypsScript がエミュレートしているコードを見ても仕方ないかもしれない、そもそも async/await の仕様自体まだ固まってない) ただ、今の時点でこれまでよりずっと直感的な記述が可能になっているし、この後そこまで大きな変更はない?かもしれない。tranpiler で使うのもいいけどはやく普通に使いたいですね。