created_at
updated_at
tags
toc

Promise.prototype.finally

Intro

Promise.prototype.finally の仕様が TC39 stage 3 となり、Safari TP37 で先行実装が入った。

tc39/proposal-promise-finally

common task in async task

よくあるユースケースとして、fetch() 中にスピナーを表示し、終わったら消すという場合。

スピナーは、fetch() が成功(resolve) しようと失敗(reject)しようと消したいため、これまでの Promise では両方のハンドラに処理が必要だった。

showSpinner()
fetch()
  .then((response) => {
    hideSpinner()
    console.log(response)
  })
  .catch((error) => {
    hideSpinner()
    console.log(error)
  })

finally()

finally() は、resolve/reject どちらでも実行されるので、こう書くことができる。

showSpinner()
fetch()
  .then((response) => {
    console.log(response)
  })
  .catch((error) => {
    console.log(error)
  })
  .finally(() => {
    hideSpinner()
  })

finally() には引数は渡って来ない。(来たとしても、それが resolve/reject どちらの結果か判別できないため)

finally() の戻り値

また、finally() はその前の Promise の結果をそのまま戻す。つまり、以下のように先に書いても問題ない。

showSpinner()
fetch()
  .finally(() => {
    hideSpinner()
  })
  .then((response) => {
    console.log(response)
  })
  .catch((error) => {
    console.log(error)
  })

これで、response/error の処理に時間がかかるとしても、まず Snipper を消すという処理を完了させられる。

async/await

なお、async/await を使った場合は、try-catch-finally がそのまま使えるため、この仕様とは関係なく以下のように書ける。

(async () => {
  try {
    res = await fetch('/')
    console.log(res)
  } catch (e) {
    console.error(e)
  } finally {
    hideSpinner()
  }
})()

DEMO

動作するデモを以下に用意した。