Terada's Blog

フリーランスでソフトウェア開発してます。プログラミング、競プロ、スタートアップなどについて

初心者向けjavascriptのPromise, await, async等の使い方

 どうもーーー

 

たまに投稿すると見せかけて急に技術的なことを適当に書きます。

 

その前に、最近といえば、9月に転職し、辞めて10月からなんちゃってフリーランスとしてdjangoやvuejsなどをやらせてもらっております。

 

プログラミングを始めたのが、2年ちょっと前で、今までchatbotの開発がメインでしたが、なんとかwebの開発にも貢献できています。(もちろん至らぬ所だらけ)

 

pythonはchatbot開発に使用していたので、djangoは文法という意味では苦労しなかったですが、フレームワークの仕組みになれるのはちょっと難しかったです。

 

jsは今までフロントを一切作ってこなかったため、本当に初めての状態で現場に入らせてもらいましたが、vuejsを含めだいぶ扱えるようになってきました。(もちろん至らぬ所だらけ)

 

さて、今回は、非同期処理を実装する上でわかりやすい記事が少なかったため、自分なりにまとめてみようと思います。

 

そもそも非同期処理とは?

非同期とは、英語で言えばasynchronousですが(意味はないが英語にしてみる)、 つまりは複数の処理があるときに、それぞれの処理が完了してから次の処理を行うのではなく、 1つの処理を始めたら終わる前に次の処理をやるようなイメージです。 まあ詳しい定義とかは省きます。 使用したいタイミングとしては、例えばどこかから天気を取得するために通信を行うとして、その間に他の処理を行いたい場合です。 通信に関しては時間がかかりますから、いちいち天気情報の取得が完了してから処理を行っていてはスピードが遅くなってしまいます。 そこで、天気を取得するAPIを呼んだあとに、結果が取得できるまでに他のAPIをまた呼んだりするということです。 具体例を見てみましょう。(はてなにコード埋め込むのが初めてで見にくい)


function asyncFunc() {
  return new Promise(resolve => {
    setTimeout(function() {
      console.log('resolving')
      resolve()
    }, 1000) 
  })
}

function callAsyncFunc() {
  console.log('start')
  asyncFunc()
  .then(() => {
    console.log('after resolving')
  })
  console.log('after calling asyncFunc')
}

callAsyncFunc()
    

こちら、上のasyncFunc()が時間のかかる処理です。 callAsyncFunc()内で時間のかかる処理を呼び出し、終わったらconsole出力をしています。 こちらの出力は以下になります。


start
after calling asyncFunc
resolving
after resolving

  

ポイントとしては、時間のかかる処理にPromiseを使うことです。(一説によるとPromiseという命名には某消費者金融機関からの圧力があったとかなかったとか) まあこいつが初めてだとなんじゃこいつはああああってなりますよね。 上の関数asyncFunc()では、

return new Promise()

というように、ただPromiseクラスのインスタンスを返しているだけです。 要は以下のような書き方のほうがわかりやすいかもしれません。


function hoge() {
  let promise = new Promise(resolve => {
    setTimeout(function() {
      console.log('resolving')
      resolve()
    }, 1000)
  })
  return promise
}

このようにpromiseクラスのインスタンスを返す関数は、非同期処理となります。 Promiseオブジェクトのインスタンスを作成する際に、引数に行いたい処理を記述します。 天気の例だと、ここで天気情報を取得するAPIを呼び出したりです。 今回の例では、単に1秒待ってからconsoleに出力をしています。 そしてまた気持ち悪いのがこのresolve()というやつですよね。 このresolve()は、まあ英語で言ったら解決という意味ですから、非同期処理が解決したということを示す役割でしょう。 つまり、非同期処理をPromiseを使用して作成した場合、resolveで終わりましたよ〜ということを示してやらないといけません そうでなければ、天気情報を取得し終わったら画面に表示したい、といったことが実現できません。 さて、ダラダラと書いていますが(そもそも人にわかってもらおうとも思っていませんから)、


asyncFunc()
  .then(() => {
    console.log('after resolving')
  })

こちらのthenについてです。 このthenというのは、Promiseのインスタンスがresolveしたときに呼び出されます。 つまり、この場合、わかりやすく書くとこうなります。(最初からわかりやすく書け)


function callAsyncFunc() {
  console.log('start')
  let promise = asyncFunc()
  promise.then(()=>{
    console.log('inside then')
  })
  console.log('after calling asyncFunc')
}

このように、promiseのインスタンスにthenをつければまあいとも簡単に、非同期処理が終わってから何かの処理をするということができます。

これ以上の説明もそんなに必要ないと思うので、あとは何パターンかpromiseの使い方を載せておきます。(説明がめんどくなった)


function getRandomInt(max) {
  return Math.floor(Math.random() * Math.floor(max));
}

function asyncFunc() {
  return new Promise(resolve => {
    let interval = 1000*getRandomInt(3)
    setTimeout(function() {
      console.log('resolving')
      resolve()
    }, interval) 
  })
}

function callAsyncFunc() {
  console.log('start')
  let promises = []
  let promise1 = asyncFunc()
  promises.push(promise1)
  let promise2 = asyncFunc()
  promises.push(promise2)
  Promise.all(promises)
    .then(()=>{
    console.log('inside then')
  })
  console.log('after calling asyncFunc')
}

callAsyncFunc()

出力


start
after calling asyncFunc
resolving
resolving
inside then

こちらは、2つの非同期処理を開始し、2つともが終わった時点で処理を行うようなものです。 もちろん、それぞれが終わったタイミングで呼び出したい処理があれば、それをpromise1.then()のような形で書いておけばどちらも実行されます。

forループで何回も呼び出したいとき


function getRandomInt(max) {
  return Math.floor(Math.random() * Math.floor(max));
}

function asyncFunc() {
  return new Promise(resolve => {
    let interval = getRandomInt(3)
    setTimeout(function() {
      console.log('resolving')
      resolve()
    }, 1000*interval) 
  })
}

function callAsyncFunc() {
  console.log('start')
  let promises = []
  for (i=0; i<=5; i++) {
           let promise = asyncFunc()
           promises.push(promise)
           }
           Promise.all(promises)
           .then(()=>{
    console.log('inside then')
  })
  console.log('after calling asyncFunc')
}

callAsyncFunc()

出力



start
after calling asyncFunc
resolving
resolving
resolving
resolving
resolving
resolving
inside then

こちらは、for文でpromiseを作りまくって、全部終わったら何かの処理をしたい、みたいな感じです。

 

 


function asyncFunc() {
  return new Promise(resolve => {
    setTimeout(function() {
      console.log('resolving')
      resolve()
    }, 1000) 
  })
}

async function callAsyncFunc() {
  console.log('start')
  await asyncFunc()
  .then(()=>{
    console.log('inside then')
  })
  console.log('after calling asyncFunc')
}

callAsyncFunc()


出力


start
resolving
inside then
after calling asyncFunc

こちらはawaitというものを使用しています。 awaitは、promiseを返す関数の呼び出しにつけると、その関数が返すpromiseがresolveするまであとの処理を待ってくれます。 awaitを使う場合は、awaitを使う関数の定義時にasyncをつける必要があります。 まあこれはつけてなかったら例外が飛んでくるので気にしなくてもいいでしょう。

awaitで各ループを待ちたい場合は以下のようにします。


function getRandomInt(max) {
  return Math.floor(Math.random() * Math.floor(max));
}

function asyncFunc() {
  return new Promise(resolve => {
    let interval = getRandomInt(3)
    setTimeout(function() {
      console.log('resolving')
      resolve()
    }, 1000*interval) 
  })
}

async function callAsyncFunc() {
  console.log('start')
   for (i=0; i<=5; i++) {
    await asyncFunc()
      console.log('looping', i)
    }
    console.log('after calling asyncFunc')
  }

callAsyncFunc()

出力


start
resolving
looping 0
resolving
looping 1
resolving
looping 2
resolving
looping 3
resolving
looping 4
resolving
looping 5
after calling asyncFunc

疲れました。以上です。