JavaScriptのasync/awaitはPromiseを使った非同期感たっぷりのコードを同期処理っぽくしてくれるが、async/awaitを理解するには、Promiseも知る必要がある。そこで、Promiseからおさらいしておこうと思う。
構文
まず基本的な構文。どうやってPromiseを作成するのか?
function 非同期的な関数(成功時コールバック, 失敗時コールバック) {
if (...) {
成功時コールバック(成果)
} else {
失敗時コールバック(問題)
}
}
// ↓executor
new Promise(function (resolve, reject) {
非同期的な関数(
(成果) => resolve(成果), // 成功時コールバック関数
(問題) => reject(問題), // 失敗時コールバック関数
)
})
-
Promise
クラスをnew
して使う。 - コンストラクタの引数
- 引数は
executor
1つだけ -
executor
はFunction
型-
resolve
はexecutor
に渡ってくる関数- 非同期な処理が成功したとき、
resolve
に成果となる値を渡す。
- 非同期な処理が成功したとき、
-
reject
はexecutor
に渡ってくる関数- 非同期な処理が失敗したとき、
reject
に問題となるエラーオブジェクトなどを渡す。
- 非同期な処理が失敗したとき、
-
- 引数は
例: axiosとPromise
/**
* サーバの死活監視。ステータスコードを返す。
* @returns {Promise.<number>}
*/
function getServerStatusCode() {
return new Promise(function(resolve, reject) {
axios
.get("https://httpbin.org/status/200")
.then(response => resolve(response.status))
.catch(error => reject(error.response.status))
});
}
getServerStatusCode()
.then(statusCode => console.log("生きてる", statusCode))
.catch(statusCode => console.error("死んでる", statusCode))
- CodePen → Axios and Promise
- これはちょっと冗長なコードになっている。もっとすっきり書く方法は後述する。
Promise.resolve
new Promise(function (resolve, reject) {
resolve(1)
})
このコードは下のコードに書き換えることができる
Promise.resolve(1)
なので、then
などとメソッドチェーンもできる
Promise.resolve(1)
.then(value => {
console.log(value)
})
asyncとawait - 非同期処理を同期処理っぽく書く
ご覧の通り、非同期処理の結果はthen
にコールバックを与えないと処理できない。同期処理のコードと比べると一手間必要だ。
/**
* @returns {Promise.<number>}
*/
function 非同期処理() {
return Promise.resolve(1)
}
/**
* @returns {number}
*/
function 同期処理() {
return 1
}
function main() {
console.log(1 + 非同期処理()) // 1[object Promise]
console.log(1 + 同期処理()) // 2
// Promiseの結果の処理はthenが必要
非同期処理().then(value => console.log(1 + value)) // 2
}
main()
asyncとawaitを使うと同期処理っぽく書けるようになる。
-
await
→ Promiseの値が取り出されるまで待つ。 -
async
→await
キーワードを使っている関数のあたまに付ける必要がある。
/**
* @returns {Promise.<number>}
*/
function 非同期処理() {
return Promise.resolve(1)
}
async function main() {
console.log(1 + 非同期処理()) // 1[object Promise]
console.log(1 + await 非同期処理()) // 2
}
main()
例: axiosとasync/awaitによる同期的な書き方
/**
* サーバの死活監視。ステータスコードを返す。
* @returns {Promise.<number>}
*/
async function getServerStatusCode() {
try {
return (await axios.get("https://httpbin.org/status/500")).status
} catch (error) {
throw error.response.status
}
}
getServerStatusCode()
.then(statusCode => console.log("生きてる", statusCode))
.catch(statusCode => console.error("死んでる", statusCode))
- 前述したaxiosとPromiseの例をasync/awaitに一部書き換えたもの。
- CodePen → Axios and async
async functionはPromiseを返す
asyncはawaitとセットで使う必要はない。単体の機能としては関数をPromise
を返す関数に変身させること。
/**
* @returns {Promise.<number>}
*/
function getNumber1() {
return Promise.resolve(1)
}
/**
* @returns {Promise.<number>}
*/
async function getNumber2() {
return 1
}
console.log(getNumber1()) // Promise { 1 }
console.log(getNumber2()) // Promise { 1 }
async function内で例外を飛ばしたらどうなるか?
async function内で例外をthrowした場合でも、あくまで関数の戻り値はPromise
型。例外も.catch()
で処理できる。
/**
* @returns {Promise.<void>}
*/
async function throwError() {
throw "err"
}
throwError().catch(console.log) // errと出力される
async function内でPromiseを返したらどうなるか?
戻り値がPromiseなら、そのまま返される。なので、Promiseが二重になることはない。
/**
* async functionなのにPromiseをreturnする関数
* @returns {Promise.<number>}
*/
async function promise() {
return Promise.resolve(1)
}
/**
* @returns {Promise.<number>}
*/
async function number() {
return 1
}
promise().then(console.log) // 1
number().then(console.log) // 1
これはasync functionの性質というより、Promiseの性質のようだ。Promise.resolve
をいれこにしてもPromise
が多重にならない。
console.log(
Promise.resolve(
Promise.resolve(
Promise.resolve(1)
)
)
) //=> Promise { 1 }
finallyはPromiseにもある?
async/awaitだとfinallyで成功時・失敗時どちらでも共通の処理を実行できる。
/**
* @returns {Promise.<void>}
*/
async function getStatusCode() {
try {
console.log('started')
const response = await axios.get('https://httpbin.org/status/200')
console.log('success', response.status)
} catch (error) {
console.log('error', error.response.status)
} finally {
console.log('finished') // 共通処理
}
}
これと同じことはPromiseでもできるのだろうか?
function getStatusCode() {
console.log('started')
return axios.get('https://httpbin.org/status/200')
.then(response => {
console.log('success', response.status)
})
.catch(error => {
console.log('error', error.response.status)
})
.finally(_ => {
console.log('finished') // !?!?
})
結論を言うと、無いことはないがPromise.prototype.finally() - JavaScript | MDNに書いてある通り、2019-05-28現時点でChromeやEdgeをはじめとした多くのブラウザが対応しているが、Internet Explorerは未対応である。なお、babel-polyfilが入っていれば、finallyも使えるようだ。
Promiseでもそんなにコードの保守性悪くないのでは?
ここまで見てきたサンプルコードでは、Promiseを使っていてもそこまで保守性を損ねそうにない。では、どういうときにasync & awaitのほうがコードがシンプルになるのだろうか。それは、2段以上Promiseのthenが重なるときだと思う。例えば次のようなコード。
function main() {
getX().then(x => {
getY().then(y => {
console.log(x + y)
})
})
}
function getX() {
return Promise.resolve(1)
}
function getY() {
return Promise.resolve(2)
}
main()
これをasync & awaitに書き換えるとすっきりする。
async function main() {
const x = await getX()
const y = await getY()
console.log(x + y)
}
async function getX() {
return 1
}
async function getY() {
return 2
}
main()
今日現在のPromiseにはmapが生えていない
- Promiseにはmapメソッドが生えていない。
- サードパーティでそれを可能にするライブラリがあるかも? 探してない。
参考文献
最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします→Twitter@suin