かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript 今更の async / await

async function は Promose を返す

  • returnすると返される値がPromise.resolveされる
  • throwすると値や例外がPromise.rejectされる
  • 何も返さないと関数終了時にundefinedreturn(resolove)される
// return = Promise.resolve()
async function resolveFunc() {
  return 'this resolve';
}

resolveFunc()
.then((res) => console.log(res))
.catch((err)=> console.log(`ERROR ${err}`));
// => this resolve


// throw = Promise.reject()
async function rejectErrorFunc() {
  throw new Error('throw Error');
}

rejectErrorFunc()
.then((res) => console.log(res))
.catch((err)=> console.log(`ERROR ${err}`));
// => ERROR Error: throw Error

// 値をthrow しても reject 扱い
async function throwValueFunc() {
  throw 'throw Value';
}

throwValueFunc()
.then((res) => console.log(res))
.catch((err)=> console.log(`ERROR ${err}`));
// => ERROR throw Value

// return なら返す値が Error オブジェクトでも resolve
async function returnErrorFunc() {
  return new Error('retun Error');
}

returnErrorFunc()
.then((res) => console.log(`RESOLVE ${res}`))
.catch((err)=> console.log(`ERROR ${err}`));
// => RESOLVE Error: retun Error

// 何も返さない関数
// 関数終了時に undefined が resolve される
async function noReturnFunc() {
  // do't return anything
}

noReturnFunc()
.then((res) => console.log(`RESOLVE ${res}`))
.catch((err)=> console.log(`ERROR ${err}`));
// => RESOLVE undefined

await は何をしているか

  • awaitキーワードはasync function内でしか使用できない
  • awaitキーワードで呼び出した関数からPromiseが返されるまで、自身のあるasync functionの処理を止めて待機する
function waitSquareFunc(x) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(x * x);
    }, 500);
  });
}

// awaitキーワードはasync内でしか使用できない
async function getSquareAndAdd(x, y) {
  // awaitキーワードがあると値が帰ってくるまで処理が止まる
  const res = await waitSquareFunc(x);
  // Promise.resolve
  return res + y;
}

// async function を呼び出しているので Promise が返される
getSquareAndAdd(2, 1)
  .then((res) => console.log(res))
  .catch((err) => console.log(err));
// => 5

// async / await で何も返さない場合も関数が終了時の undefined が resolve される
async function awaitNoReturnFunc() {
  // ここでPromiseが返されるまで待機して
  const res = await waitSquareFunc(2);
  // return undefined される
}

awaitNoReturnFunc()
.then((res) => console.log(`RESOLVE ${res}`))
.catch((err)=> console.log(`ERROR ${err}`));
// => RESOLVE undefined

Promiseで書くとこんな感じ

function waitSquareFunc(x) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(x * x);
    }, 500);
  });
}

function getSquareAndAddByPromise(x, y) {
  return waitSquareFunc(x)
    .then((res) => {return res + y})
    .catch((err) => {throw err});
}

getSquareAndAddByPromise(2, 1)
  .then((res) => console.log(res))
  .catch((err) => console.log(err));
// => 5

エラーハンドリング

try-catchでエラーハンドリング

// isError ... true -> Promise.reject / false -> throw new Error
function throwErrorFunc(isError) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      try {
        if( isError ) {
          reject('Reject ERROR');
        } else {
          // throw new Error されると catch 節に処理が流れる
          throw new Error('Throw new ERROR');
          console.log('ココは実行されない');
          resolve('NO ERROR');
        }
      } catch(e) {
        console.log('> ERROR CATCH');
        reject(e.message);
      }
    }, 500);
  });
}

async function errorHandlingTry_catch(isError) {
  try {
    const res = await throwErrorFunc(isError);
    console.log('>> NOT IN CATCH');
    return res;
  } catch(err) {
    console.log('>> TRY-CATCH ERROR');
    throw err;
  }
}

// throwErrorFunc で reject error
errorHandlingTry_catch(true)
  .then((res) => console.log(res, 'then'))
  .catch((err) => console.log(err, 'catch'));
// => >> TRY-CATCH ERROR
// => Reject ERROR catch

// throwErrorFunc で throw new Error
errorHandlingTry_catch(false)
  .then((res) => console.log(res, 'then'))
  .catch((err) => console.log(err, 'catch'));
// => > ERROR CATCH
// => >> TRY-CATCH ERROR
// => Throw new ERROR catch

async function 内ではtry-catchしなくてもawaitで呼び出されている関数でPromise.rejectされると、そのままasync functionの呼び出し元にthrowされる

async function errorHandling(isError) {
  const res = await throwErrorFunc(isError);
  console.log('NOT IN CATCH');
  return res;
}

// throwErrorFunc で reject error
errorHandling(true)
  .then((res) => console.log(res, 'then'))
  .catch((err) => console.log(err, 'catch'));
// => Reject ERROR catch

// throwErrorFunc で throw new Error
errorHandling(false)
  .then((res) => console.log(res, 'then'))
  .catch((err) => console.log(err, 'catch'));
// => > ERROR CATCH
// => Throw new ERROR catch

async / await で Fetch してみる

// fetch でエラーになるとPromise.rejectになるのでtry-catchは省略可
const getDataByFetchAPI = async () => {
  const res = await fetch(api);
  // fetchAPIは res.json() で Promiseを返すのでawaitを2重にする必要がある
  const data = await res.json();
  return data;
};

getDataByFetchAPI()
  .then((res) => console.log(res))
  .catch((err) => console.log(err.message));

まとめ

遥か昔のCallback hellなExpressプロジェクトを見ていたので、JavaScriptの非同期処理すごく見やすく書けるようになったんだね。ほえーって感じです。(Promiseでも十分見やすいですよ...

asyncは必ずしもawaitを使う必要はなく、Promiseを返す関数。
awaitを使えばコールバックなネストになった文でなく非同期処理の待機を書けるから完結でとても便利だけど、処理を止めるという意識がないと不用意にループの中で使って処理時間が長くなってしまうとかありそうなので、awaitが何をしているのか理解して並行処理したほうが良いのかとか設計するのが良さそうって思いました。

Promiseの記事を書いた時に次はasync/awaitだ!って言ったままだったので、書けてよかった。 (Promise.resolve


[参考]