TypeScriptでJSONP APIを呼ぶ関数のサンプル(Promise利用)

TypeScriptでJSONP APIを呼ぶ関数が必要になったので作成してみました。

まずは、anyを許容するサンプルです。

// JSONP APIを呼ぶ関数
function callJSONP(url: string) {
  return new Promise((resolve, reject) => {
    // JSONPのスクリプトを呼ぶためのscriptタグの生成
    let script = document.createElement("script");
    // グローバル変数に割り当てるコールバック用関数の変数名
    let name = "jsonp_" + Date.now();

    if (url.match(/\?/)) url += "&callback=" + name;
    else url += "?callback=" + name;

    script.src = url;
    script.addEventListener("error", reject);
    // グローバル変数に、JSONPで呼び出されるコールバック関数を定義
    (window as any)[name] = (json: any) => {
      // JSONPで渡されたJSONをresolveメソッドで渡す
      resolve(json);
      // JSONP呼び出し用のタグを削除
      script.remove();
      // グローバル変数に割り当てたコールバック関数を削除
      delete (window as any)[name];
    };

    // JSONPを呼び出すスクリプトタグを追加
    document.body.appendChild(script);
  });
}

// 大阪城の郵便番号
const postCode = "5400002";
const res = (await callJSONP(
  `https://zipcloud.ibsnet.co.jp/api/search?zipcode=${postCode}`
)) as {
  status: number;
  results: {
    zipcode: string;
    address1: string;
    address2: string;
    address3: string;
  }[];
};
// 住所を表示
console.log(
  `${res.results[0].address1}${res.results[0].address2}${res.results[0].address3}`
); // 大阪府大阪市中央区大阪城

利用したサンプルAPIは、郵便番号を指定して住所を返してくれるJSONP APIです。
郵便番号検索API – zipcloud

JSONPの仕組みは、指定の関数名を実行する外部JavaScriptのようなものです。呼び出された関数の引数に取得したい値が入ります。
なので、グローバルに関数を登録する必要があるわけですが、TypeScriptだと単純にグローバル関数を登録できません。
なぜなら、ブラウザーのJavaScriptのグローバルオブジェクトであるWindowには、「Window & typeof globalThis」という型が割り当てられており、自由に関数を追加できないためです。

というわけで、(window as any)という指定をして、一時的にwindowオブジェクトはany型ということにしています。これで、どんな値も入れることができます。

そして、どんな引数にどんな値が入っているか分からないので、こちらもanyとしています(といっても、どうやらこの関数の返り値の型は、「Promise」ではなく、「Promise」となるようなので、asで型変換しています)。

ただ、型にanyを使うことはできないという人もいると思います。なので、anyを使わない実装も考えてみました。

// JSONP APIを呼ぶ関数
function callJSONP<T extends {} = {}>(url: string) {
  return new Promise<T>((resolve, reject) => {
    // JSONPのスクリプトを呼ぶためのscriptタグの生成
    let script = document.createElement("script");
    // グローバル変数に割り当てるコールバック用関数の変数名
    let name = "jsonp_" + Date.now();

    if (url.match(/\?/)) url += "&callback=" + name;
    else url += "?callback=" + name;

    script.src = url;
    script.addEventListener("error", reject);

    const jsonpWindow = window as unknown as {
      [index: string]: (json: T) => void;
    };
    // グローバル変数に、JSONPで呼び出されるコールバック関数を定義
    jsonpWindow[name] = (json: T) => {
      // JSONPで渡されたJSONをresolveメソッドで渡す
      resolve(json);
      // JSONP呼び出し用のタグを削除
      script.remove();
      // グローバル変数に割り当てたコールバック関数を削除
      delete jsonpWindow[name];
    };

    // JSONPを呼び出すスクリプトタグを追加
    document.body.appendChild(script);
  });
}

// 大阪城の郵便番号
const postCode = "5400002";
const res = await callJSONP<{
  status: number;
  results: {
    zipcode: string;
    address1: string;
    address2: string;
    address3: string;
  }[];
}>(`https://zipcloud.ibsnet.co.jp/api/search?zipcode=${postCode}`);
// 住所を表示
console.log(
  `${res.results[0].address1}${res.results[0].address2}${res.results[0].address3}`
); // 大阪府大阪市中央区大阪城

windowの型を「{[index: string]: (json: T) => void;}」としています。JSONPで呼び出される関数の引数は、ジェネリクスを用いて関数呼び出しの際に型を指定するようにしました。
これで、anyを使わずにJSONP APIを呼べます。

コメント

タイトルとURLをコピーしました