Edited at

Promiseの使い方、それに代わるasync/awaitの使い方

JavaScriptのasync/awaitはPromiseを使った非同期感たっぷりのコードを同期処理っぽくしてくれるが、async/awaitを理解するには、Promiseも知る必要がある。そこで、Promiseからおさらいしておこうと思う。


構文

まず基本的な構文。どうやってPromiseを作成するのか?

function 非同期的な関数(成功時コールバック, 失敗時コールバック) {

if (...) {
成功時コールバック(成果)
} else {
失敗時コールバック(問題)
}
}

// ↓executor
new Promise(function (resolve, reject) {
非同期的な関数(
(成果) => resolve(成果), // 成功時コールバック関数
(問題) => reject(問題), // 失敗時コールバック関数
)
})



  • Promiseクラスをnewして使う。

  • コンストラクタの引数


    • 引数はexecutor1つだけ


    • executorFunction



      • resolveexecutorに渡ってくる関数


        • 非同期な処理が成功したとき、resolveに成果となる値を渡す。




      • rejectexecutorに渡ってくる関数


        • 非同期な処理が失敗したとき、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


  • :warning: これはちょっと冗長なコードになっている。もっとすっきり書く方法は後述する。


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の値が取り出されるまで待つ。


  • asyncawaitキーワードを使っている関数のあたまに付ける必要がある。

/**

* @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メソッドが生えていない。

  • サードパーティでそれを可能にするライブラリがあるかも? 探してない。


参考文献