mizdra's blog

ぽよぐらみんぐ

Chrome拡張機能でコールバック地獄を解決する

Chrome拡張機能の非同期APIはコールバックにより実装されています. 例えば, 拡張機能ごとに用意されるストレージからデータを取得する場合, ストレージへのアクセス中にJavaScriptの処理が中断されるのを防ぐため, 非同期APIが用意されています.

// background.js
chrome.storage.local.get(['admin'], (result1) => {
  chrome.storage.local.get([`user/${result1.admin}/name`], (result2) => {
    console.log(result2)
  })
})

このコールバックによる非同期APIはJavaScriptにおいては一般的な非同期APIの実装手法ですが, 「コールバック地獄」という問題を引き起こします. これを解決する手法としてES2015で追加されたPromiseとジェネレータ, もしくはES2017で追加されたasync/awaitを利用する手法が知られています.

numb86-tech.hatenablog.com

numb86-tech.hatenablog.com

susisu.hatenablog.com

しかしChrome拡張機能の非同期APIはコールバックにより実装されているため, Chrome拡張機能の中では直接的にはPromiseasync/awaitを利用することが出来ません. そのため, 手動で非同期APIをPromiseでWrapするか, 以下のようなWrapperライブラリを利用する必要があります.

この記事ではmozilla/webextension-polyfillを使ってコールバック地獄を解決する方法を紹介します.

mozilla/webextension-polyfillを使ってコールバック地獄を解決する

ブラウザ間で統一的なAPIで拡張機能を作成できるよう策定されたWeb Extension APIというものがあり, 現時点でMozilla FirefoxとMicrosoft Edgeでサポートされています. mozilla/webextension-polyfillはこのWeb Extension APIのChrome向けのPolyfillとなっています. Web Extension APIの非同期APIは全てPromiseで提供されているため, Chrome拡張機能でコールバック地獄を解決する方法として用いることができます.

mozilla/webextension-polyfillnpm.comからもインストールできますが, モジュールバンドラなどを挟む必要があるため, 少々手間です. これに変わるより手軽なものとして, unpkg.comから直接ライブラリをダウンロードし, manifest.jsonbackground.scriptsなどから読み込む方法があります.

$ cd extension-project
$ wget https://unpkg.com/webextension-polyfill/dist/browser-polyfill.min.js
// manifest.json
{
  // ...

  "background": {
    "scripts": [
      "browser-polyfill.min.js",
      "background.js"
    ]
  },

  "content_scripts": [{
    // ...
    "js": [
      "browser-polyfill.min.js",
      "content.js"
    ]
  }]
}

このようにPolyfillを読み込むことでbackground.jsなどからbrowserという名前空間からWeb Extension APIにアクセスできます.

// background.js
browser.storage.local.get(['admin'])
  .then((result) => {
    return browser.storage.local.get([`user/${result.admin}/name`])
  })
  .then((result) => {
    console.log(result)
  })

非同期APIはPromiseを返すので, 次のようにasync/awaitを利用したコードにも書き換えることもできます.

// background.js
(async () => {
  const result1 = await browser.storage.local.get(['admin'])
  const result2 = await browser.storage.local.get([`user/${result1.admin}/name`])
  console.log(result2)
})()

以上, Chrome拡張機能でコールバック地獄を回避するTIPSでした.