JavaScriptライブラリの初期化完了やJSONPのコールバック関数をPromise化する

Google Maps JavaScript APIの初期化完了をPromiseで待機したかったので、方法を考えてみた。

グローバル環境が少し汚れるが、一般にJSONPのコールバックをPromise化できる。

やり方

以下のやり方で、callbackの実行時にコンソールに"SOLVED"と表示できる。

let globalresolve;
function callback(){
  globalresolve();
}
const p1 = new Promise((resolve) => {globalresolve = resolve;});

p1.then(() => {
  console.log("SOLVED");
});
  1. グローバルにresolve保存用の変数(例:globalresolve)を作っておく
  2. Promise生成時に引数のresolveglobalresolveに代入する
  3. コールバック関数でglobalresolveを呼び出す

という流れでPromise化できる。

使用例1

Google Maps JavaScript APIの読み込み完了時に関数initMapを呼ぶようにする場合。

let mapresolve;
function initMap() {
  mapresolve();
}

(new Promise((resolve) => {mapresolve = resolve;})).then(() => {
  console.log("SOLVED")}
);
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"></script>

使用例2

単体でPromise化してもあまり恩恵は大きくないのだが、複数のライブラリを使用していてすべてのライブラリの読み込み完了後に何かしら処理をしたい、 といった場合にPromise.allを使って並列して読み込みつつ、すべてが完了するのを待機してから次の処理に移る、といったことが可能。

let gr1, gr2;
function callback1(){
  gr1();
}
function callback2(){
  gr2();
}

const p1 = new Promise((r) => {gr1 = r;});
const p2 = new Promise((r) => {gr2 = r;});

Promise.all([p1, p2]).then(() => {
  console.log("ALL DONE");
});

できる限りグローバルを汚したくない場合

一応、細かくスコープを区切ればグローバルに保管用変数を追加しなくてもPromise化可能(callbackの巻き上げが起こらないので、callback = () => outerresolve();が実行される前にコールバック関数が呼び出されてしまうとNGな点は注意)。

let callback;
{
  let outerresolve;
  callback = () => outerresolve();

  (new Promise((resolve) => {outerresolve = resolve;})).then(() => {
    console.log("SOLVED")}
  );
}

参考