JavaScript
TypeScript

async await の使い方

More than 1 year has passed since last update.

これからはasync,awaitが読み書きできないと生きていけなそうだったので調べてみました。
※コード例は一応TypeScriptですが、ほとんどJavaScriptです。
参考: async function - JavaScript | MDN

async

asyncを関数の宣言の前に付けると、その関数は必ずPromiseを返します。
すなわち、呼び出して結果を取得するためには必ず関数().then(result=>{ ... })を使うようになります。

before
function fn() {
  return 42;
}

let result = fn();
console.log(result);
after
async function fn() {
  return 42;
}

fn().then(result => {
  console.log(result);
});

async関数でPromise以外の値を返した場合は、その値でresolveされるPromiseが返るようになります。
この挙動はどこかで見覚えがあります。そう、Promise.then(result=>{ ... })...部分と同じです!

もちろん、async関数で今まで通りの方法でPromiseを返すこともできます。

async function fn() {
  return new Promise((resolve, reject) => {
    resolve(42);
  });
}

fn().then(result => {
  console.log(result);
});

await

asyncを付けた関数の中では、await PromisePromise.then(result=>{ ... })を簡略化した書き方として使えるようになります。async関数の中ではないトップレベル等の場所ではこの書き方はできないので注意しましょう。

before
async function fn() {
  return 42;
}

async function exec() {
  fn().then(result => {
    console.log(result);
  });
}

exec();
after
async function fn() {
  return 42;
}

async function exec() {
  let result = await fn();
  console.log(result);
}

exec();

これにより、Promise.then()のチェインが通常の同期処理の流れと同じように書くことができるようになります。

before
// delayミリ秒待機する。任意の第二引数を結果として返す。
async function sleep(delay, result?) {
  return new Promise(resolve => {
    setTimeout(() => resolve(result), delay);
  });
}

async function exec() {
  sleep(1000)
    .then(() => console.log(1))
    .then(() => sleep(2000, 42))
    .then((result) => console.log(result))
}

exec();
after
// delayミリ秒待機する。任意の第二引数を結果として返す。
async function sleep(delay, result?) {
  return new Promise(resolve => {
    setTimeout(() => resolve(result), delay);
  });
}

async function exec() {

  // 非同期処理を実行するだけ
  await sleep(1000)
  console.log(1);

  // 非同期の結果を受け取る
  let result = await sleep(2000, 42)
  console.log(result);
}

exec()

この書き方では、Promiseの処理を順番に処理したり並行処理したりするのを簡単に書き分けることができます。従来の方法では並行処理はPromise.all()で書けますが、順番に処理するのは結構大変でした。なので、これはかなり便利です。

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function add1(x) {
  var a = resolveAfter2Seconds(20);
  var b = resolveAfter2Seconds(30);
  return x + await a + await b; // 2秒待つのと2秒待つのが並行で行われる
}

add1(10).then(v => {
  console.log(v);  // 2秒後に60と表示
});

async function add2(x) {
  var a = await resolveAfter2Seconds(20); // ここで2秒待つ
  var b = await resolveAfter2Seconds(30); // さらに2秒待つ
  return x + a + b;
}

add2(10).then(v => {
  console.log(v);  // 4秒後に60と表示
});

もう一つのawaitのすごいところは、Promiseの例外ハンドリングをtry { ... } catch { ... }で書けるところです。

before
function getProcessedData(url) {
  return downloadData(url) // returns a promise
    .catch(e => {
      return downloadFallbackData(url) // returns a promise
    })
    .then(v => {
      return processDataInWorker(v); // returns a promise
    });
}
after
async function getProcessedData(url) {
  let v:
  try {
    v = await downloadData(url); 
  } catch (e) {
    v = await downloadFallbackData(url);
  }
  return processDataInWorker(v);
}