1. ohgiwk

    Posted

    ohgiwk
Changes in title
+Async Await in Typescript
Changes in tags
Changes in body
Source | HTML | Preview

はじめに

最近、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が使えるようになりましたね。

What's New TypeScript 2.1

v1.6から --experimentalAsyncFunctionsオプション付きでサポートを開始
v1.7からオプションなし、"target": "es6" 限定でサポート
v2.1から "target": "es5/es3"でサポート

使ってみる

2秒間待ってから "wait" と出力するwait関数を3回直列で実行するサンプルを書いてみます。

まず、Promiseで書いてみます。

promise.ts
// 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を用いた書き方で書き直してみます。

async.ts

// 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();
  }
}



とてつもなく簡単に非同期処理が書けるので、気持ちよくコードを書くことができます。

ぜひ使ってみてください。

参考
公式ドキュメント
JavaScriptは如何にしてAsync/Awaitを獲得したのか Qiita版