はじめに
今までコーディングする機会は多々あったものの、Javascriptにおける非同期処理は以前に先輩エンジニアさんたちが記述してた Promise
やら async/await
をそのまま雰囲気で真似する〜ってことがほとんどでした。
このままではまずいと思ったので自分なりに備忘録としてまとめます。執筆時点でもあやふや状態なため、間違い等ありましたご指摘していただけるととても助かります。
Promise
まず Promise
です。こいつを知らない限りは async/await
なんかには到底立ち向かえません。
特徴
Promiseは非同期処理を簡潔に記述することができ、以下の特徴を持ちます。
-
resolve()
の場合はthen,reject()
の場合はcatchとして処理を分岐できる - 複数処理を連続して行ったり、並列して行ったりできる
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(...)
みたいな感じで普通に出てくるので恐怖でした。
一見ややこしいですが、自分で書いて試してみるとなんとなく理解できました。まずは書くことから!