12
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Promiseとasync/awaitの理解があやふやだったのでまとめ

Last updated at Posted at 2018-04-19

はじめに

今までコーディングする機会は多々あったものの、Javascriptにおける非同期処理は以前に先輩エンジニアさんたちが記述してた Promise やら async/await をそのまま雰囲気で真似する〜ってことがほとんどでした。
このままではまずいと思ったので自分なりに備忘録としてまとめます。執筆時点でもあやふや状態なため、間違い等ありましたご指摘していただけるととても助かります。

Promise

まず Promise です。こいつを知らない限りは async/await なんかには到底立ち向かえません。

特徴

Promiseは非同期処理を簡潔に記述することができ、以下の特徴を持ちます。

  1. resolve() の場合はthen, reject() の場合はcatchとして処理を分岐できる
  2. 複数処理を連続して行ったり、並列して行ったりできる

1. resolve() の場合はthen, reject() の場合はcatchとして処理を分岐できる

基本的にはnew Promiseで作成したインスタンスをreturnさせて使用します。(ES6なので今回 return は省略)
以下の場合、 インスタンスにおいて resolve() を呼び出しているので .then() 内の処理が呼び出されます。


const promiseExample = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success') // 成功させるようresolveを記述
  }, 20)
})

promiseExample().then((message) => {
  console.log(`成功: ${ message }`)
}).catch((error) => {
  console.log(`失敗: ${ error }`)
})
// => 	成功: success

一方で reject() を呼び出す場合では .catch() 内の処理が呼び出されます。



const promiseExample = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('failure') // 失敗させるようrejectを記述
  }, 20)
})

promiseExample().then((message) => {
  console.log(`成功: ${ message }`)
}).catch((error) => {
  console.log(`失敗: ${ error }`)
})
// => 	失敗: failure

2. 複数処理を連続して行ったり、並列して行ったりできる

連続して行う場合

成功時に複数の処理を繋げたい場合は以下のように書きます。これはメソッドチェーンと呼ばれています。
もっと簡潔に書きたい場合は Promise.resolve() なんてのもあるみたいです。


const promiseExample = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 20)
})

promiseExample()
  .then(() => { console.log('success 1') })
  .then(() => { console.log('success 2') })
  .catch(() => { console.log('failure') })
// => 	success 1
//		success 2

並列して行う場合

また複数処理を同時に行う場合は Promise.all(array) を利用します。この場合、配列に入っているすべての処理が完了するまでその後の処理が行われないようになります。

ここで注意すべき点は、最初の2つのconstで定義したものは関数ではなくPromiseインスタンスであるということです。関数を配列内に格納するとPromiseインスタンス内の処理は呼ばれず、then()の処理しか実行されません。


const promiseExample1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise 1')
    resolve()
  }, 10)
})

const promiseExample2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise 2')
    resolve()
  }, 20)
})

Promise.all([promiseExample1, promiseExample2])
  .then(() => { console.log('success') })
  .catch(() => { console.log('failure') })
// =>	promise 1
// 		promise 2
// 		success

並列して行う場合では、どれか一つでもrejectした場合はcatch処理が呼ばれてしまいます。ただし、配列内に格納したPromiseはその後もすべて実行されます。


const promiseExample1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise 1')
    reject() // ここをrejectに変更
  }, 10)
})

const promiseExample2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise 2')
    resolve()
  }, 20)
})

Promise.all([promiseExample1, promiseExample2])
  .then(() => { console.log('success') })
  .catch(() => { console.log('failure') })
// =>	promise 1
// 		failure
// 		promise 2

async/await

ここからやっと async/await です。async/awaitはPromiseをさらに描きやすくした記法です。時代はどんどん便利になるんですね。

async

async を非同期処理を行う関数の頭に付け加えることで、非同期関数として宣言することができます。よくわからんけどすごい。

以下が一番最初に書いたPromiseをasyncに書き直したものです。


const asyncExample = async () => 'success'

asyncExample().then((message) => {
  console.log(message)
}).catch((error) => {
  console.log(error)
})
// =>	success

上のコードを見てみるとかなりスッキリしたのがわかります。 async を頭に宣言することで、Promiseインスタンスを自動で作成し、returnしたものはすべてresolve()扱いにしています。

reject()を行いたい場合はerrorをthrowします。


const asyncExample = async () => {
  throw new Error('error')
}

asyncExample().then((message) => {
  console.log(message)
}).catch((error) => {
  console.log(error)
})
// =>	Error: error

await

async/awiatでキーになるのがawaitです。以下がawaitの特徴となります。

  • awaitはasync関数の中に記述する
  • awaitにはPromiseの処理を指定する
  • awaitで指定したPromiseが終わらない限り、その後のasync関数の処理は続行されない

実際の処理は以下のようになります。これを純粋なPromiseで書いてみればわかりますが、async関数の部分がすごく読みづらくなるはずです。


const promiseExample = (value) => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(`${ value } bar`)
  }, 10)
})

const asyncExample = async () => {
  const str = await promiseExample('foo')
  return str
}

asyncExample().then((message) => {
  console.log(message)
}).catch((error) => {
  console.log(error)
})
// =>	foo bar

複数処理を連続して行う場合

awaitを利用して、関数を繋げることもできます。


const promiseExample = (value) => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(`${ value }promise`)
  }, 10)
})

const asyncExample = async () => {
  const str1 = await promiseExample('1')
  const str2 = await promiseExample('2')
  return `${ str1 } ${ str2 }`
}

asyncExample().then((message) => {
  console.log(message)
}).catch((error) => {
  console.log(error)
})
// =>	1promise 2promise

複数処理を並行して行う場合

Promise.allを使用する場合も簡潔に記述可能です。こちらもネストが深くなりづらく、可読性が非常によくなります。


const promiseExample1 = (value) => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(`promise: ${ value }`)
  }, 10)
})

const promiseExample2 = (value) => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(`promise: ${ value }`)
  }, 20)
})

const asyncExample = async () => {
  const [str1, str2] = await Promise.all([promiseExample1('1'), promiseExample2('2')])
  return [str1, str2]
}

asyncExample().then((message) => {
  console.log(message)
}).catch((error) => {
  console.log(error)
})
// =>	["promise: 1", "promise: 2"]

最後に

最近ではNuxt.jsの勉強中に async asyncData(...) みたいな感じで普通に出てくるので恐怖でした。
一見ややこしいですが、自分で書いて試してみるとなんとなく理解できました。まずは書くことから!

12
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?