はじめに
最近、Typescirptを使い始めた若輩者ですが、もう型がないと息ができないくらいTypescriptに嵌まってしまいました。
今回はTypescriptの機能の一つであるAsync/Awaitを紹介します。
ES6にはPromiseがあり、非同期処理が非常に簡単に書けるようになりました。
しかし、ECMAScriptはさらなる高みを目指しているみたいです。
より発展的に非同期処理を記述できるようにするために、Async/Awaitを提案し、すでにstage3に入ってきています。
Babelでは、babel-preset-es2017
もしくは babel-plugin-transform-async-to-generator
を利用することで使用することができるみたいです。
ECMAScriptのSupersetであるTypescriptも、Async/Awaitをサポートしています。(v1.6~)
Promiseによって、充分、非同期処理が簡単に書けるのですが、
Async/Awaitはさらに簡略で、より慣れた手続き的な書き方で書けるようになります。
動作環境
Typescript v2.1.4
Async/Awaitのサポート
今までは compilerOptionsの target を es6 に指定しないと、typescriptでAsync/Awaitをコンパイルできませんでした。
そのため、ブラウザ環境向けに利用するには、TypescirptからES6にコンパイルして、さらにBabelでトランスパイルするような過程が必要だったりしたですが、v2.1より es5/es3に指定してもコンパイルできるようになりました。
これにより、より気軽にAsync/Awaitが使えるようになりましたね。
v1.6から --experimentalAsyncFunctions
オプション付きでサポートを開始
v1.7からオプションなし、"target": "es6" 限定でサポート
v2.1から "target": "es5/es3"でサポート
使ってみる
2秒間待ってから "wait" と出力するwait関数を3回直列で実行するサンプルを書いてみます。
まず、Promiseで書いてみます。
// Promiseを返す非同期関数
function wait(): Promise<any> {
return new Promise(resolve => {
setTimeout(() => {
console.log('wait');
}, 2000);
})
}
function main() {
return wait()
.then(() => {
return wait();
})
.then(() => {
return wait();
});
}
main();
コールバックを用いた書き方よりはだいぶいいのですが、returnが多かったり、何度もthenをして、少し冗長な気がします。
上記のmain関数をAsync/Awaitを用いた書き方で書き直してみます。
// functionの前にasyncを書くとasync関数になります。
async function main() {
await wait();
await wait();
await wait();
}
main();
.then()もなく、かなりスッキリした書き方になりました。
このように、Async関数の中でPromiseを返す関数の前にawait
をつけるとその関数の処理が終わるまで待ってくれます。
Async/Awaitはこのようなシンプルな記述をだけでなく、Promiseでは難しいより慣れた手続き的な表現ができます。
Promiseでは処理の結果をresolve()でPromiseに包んで.then()のコールバック関数の引数として次の処理につなげることができますが、Async/Awaitを使えば、あたかも同期関数のように = で代入するような記述ができます。
//42がラップされたPromiseを返す関数
function foo() {
return Promise.resolve(42);
}
function main() {
foo().then(num => {
console.log(num); //=> 42
});
}
main();
async function main2() {
// あたかも普通の同期関数のように = で代入できる
const num = await foo();
console.log(num); //=> 42
// ちなみに型推論もちゃんとやってくれる
typeof(num) === 'number' //=> true
}
main2();
また、Promiseでのチェーンでは途中で処理を終了したい時などには.reject()して.catch()するなど工夫が必要です。
しかし、Async/Awaitならば、通常の手続き処理のようにif文やswitch文、変数代入に利用できるので、自在に処理を切り分けたり、中断させることができます。
async function foo() {
// if文で使ったり
if (await model.exists()) {
}
// そのまま関数に渡したり
bar(await foo());
// 返り値の配列をそのまま参照
let result = (await bar())[0]
}
async関数はそれ自体が何もしなくても自動的にPromiseを返すので、async関数の実行に続けて、.then()かawaitを書くことができます。
// Promiseを返すasync関数
async foo() {
return wait();
}
// .then()で続ける
foo().then(() => {
foo();
});
// awaitで待つ
async main() {
await foo();
}
いかがでしょうか。
その他、Async/Awaitは様々なパターンの関数やメソッドに利用できます。
// アロー関数をAsync関数化
async () => {
await this.foo();
}
// クラスのメソッドにもasyncが書ける
class Foo {
async bar() {
await this.wait();
}
static async bar() {
await wait();
}
}
とてつもなく簡単に非同期処理が書けるので、気持ちよくコードを書くことができます。
ぜひ使ってみてください。