871
726

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 3 years have passed since last update.

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

Last updated at Posted at 2018-01-05

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メソッドが生えていない。
  • サードパーティでそれを可能にするライブラリがあるかも? 探してない。

参考文献


最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします:relieved:Twitter@suin

871
726
1

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
871
726

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?