Old Sunset Days

非同期処理待ちを簡潔に書けるPromise+async/await

日付 タグ node.js カテゴリ node.js

目次

Promiseのthenチェーンが長くて見づらいのを簡潔にする

この前、Promiseを利用すると非同期処理1の完了を待って、非同期処理2に進めるという順番制御ができる話を書いた。 ( ==> Node.jsで非同期処理待ちを実現するPromise

また、thenをチェーンさせることで、多段に非同期処理待ちを繋げていくことが可能であることを、そこで確認した。

再度、その時のプログラムを見ておくと、

timer_promise_chain_test.js

function myFuncPromise(value){
  return new Promise(function(resolve,reject){
    setTimeout(function(){
    console.log(`myFuncの実行中:引数${value}`);
    console.log(`${value}ms待ち完了`);
    console.log('=======');
    resolve('処理待ちしたよ');
    }, value);
  });
}

myFuncPromise(3000)
.then((value) => {
  console.log(value);
  console.log('=>thenに処理が来ました=>');
  return myFuncPromise(2000);
})
.then((value) => {
  console.log(value);
  console.log('=>thenに処理が来ました=>');
  return myFuncPromise(1000);
})
.then((value) => {
  console.log('全部が終了');
}).catch((error) => {
  console.log('エラー発生');
  console.error(error);
});

そこまでひどいわけではないのだが、なんとなくthenが.で繋がっていくのが見にくいといえば、見にくい。

async/awaitを使ってthenチェーンをなくす

このthenチェーンをなくすためにasyncとawaitを使うと、より簡潔にコードがかける。

awaitは指定した関数のPromiseの結果(resolve or reject)が返されるまで、function内の処理を止める作用があるのだが、そのfunctionはasync(非同期関数)として定義されている必要がある。

つまり、async function内でawaitが出てくると、そこで指定された関数のPromiseの結果を待って、次に処理が進むということになる。

さて、この性質を利用して、thenチェーンを取り外した例を見てみる。

timer_promise_await_resolve.js

function myFuncPromise(value){
  return new Promise(function(resolve,reject){
    setTimeout(function(){
    console.log(`myFuncの実行中:引数${value}`);
    console.log(`${value}ms待ち完了`);
    console.log('=======');
    resolve('処理待ちしたよ');
    }, value);
  });
}

async function sample() {
  const result1 = await myFuncPromise(3000);
  console.log('result1:'+result1);
  const result2 = await myFuncPromise(2000);
  console.log('result2:'+result1);
  const result3 = await myFuncPromise(1000);
  console.log('result3:'+result1);
}

sample().then((value) => {
  console.log('全部が終了');
});

async/awaitを使ったことでthenチェーンはなくなった。
唯一の見えるthenは一番最後に「全部が終了」と見えるところだけ。
実行結果はこうなる。

$ node timer_promise_await_resolve.js
myFuncの実行中:引数3000
3000ms待ち完了
=======
result1:処理待ちしたよ
myFuncの実行中:引数2000
2000ms待ち完了
=======
result2:処理待ちしたよ
myFuncの実行中:引数1000
1000ms待ち完了
=======
result3:処理待ちしたよ
全部が終了

確かに3秒待ちの処理、2秒待ちの処理、1秒待ちの処理と順番に結果が表示されている。 もし、非同期処理完了待ちが出来ていなかったら、待ち時間の短いものから結果がconsoleに表示されてしまっていたであろう。

ここで戻りがresolveの場合を見ていたが、じゃあ、rejectの場合はどうなのかと見てみると、try-catchを使えば、rejectのエラーを捕捉することができる。

例を見てみる。

timer_promise_await_reject.js

function myFuncPromise(value){
  return new Promise(function(resolve,reject){
    setTimeout(function(){
    console.log(`myFuncの実行中:引数${value}`);
    console.log(`${value}ms待ち完了`);
    console.log('=======');
    reject('失敗例');
    }, value);
  });
}

async function sample() {
  try {
    const result1 = await myFuncPromise(3000);
    console.log(result1);
    const result2 = await myFuncPromise(2000);
    console.log(result2);
    const result3 = await myFuncPromise(1000);
    console.log(result3);
  } catch(err) {
    console.error('cathed error:' + err);
  }
}

sample().then((value) => {
  console.log('全部が終了');
});

これを実行すると、

$ node timer_promise_await_reject.js
myFuncの実行中:引数3000
3000ms待ち完了
=======
cathed error:失敗例
全部が終了

つまりrejectを大枠でtry - catchで囲っている場合は、最初のエラーでcatchに飛んで処理が終わるのだ。
もしも細かい単位でtry - catchするならば、より細かくエラーによる処理遷移を行うことができる。 timer_promise_await_reject.jsを少し書き換えてみる。

timer_promise_await_reject.js

function myFuncPromise(value){
  return new Promise(function(resolve,reject){
    setTimeout(function(){
    console.log(`myFuncの実行中:引数${value}`);
    console.log(`${value}ms待ち完了`);
    console.log('=======');
    reject('失敗例');
    }, value);
  });
}

async function sample() {
  try {
    const result1 = await myFuncPromise(3000);
    console.log(result1);
  } catch(err) {
      console.error('cathed error:' + err);
  }
  try {
    const result2 = await myFuncPromise(2000);
    console.log(result2);
  } catch(err) {
      console.error('cathed error:' + err);
  }
  try {
    const result3 = await myFuncPromise(1000);
    console.log(result3);
  } catch(err) {
    console.error('cathed error:' + err);
  }
}

sample().then((value) => {
  console.log('全部が終了');
});

$ node timer_promise_await_reject.js
myFuncの実行中:引数3000
3000ms待ち完了
=======
cathed error:失敗例
myFuncの実行中:引数2000
2000ms待ち完了
=======
cathed error:失敗例
myFuncの実行中:引数1000
1000ms待ち完了
=======
cathed error:失敗例
全部が終了

今度は各awaitごとで出てくるエラー処理が行われる。
今回はこれらの例を通じて動作を見てきて、thenチェーンよりは可読性を簡潔にできることがわかった。