🦕

DenoでKyを使って極楽要求(しなさい)

2021/06/22に公開

公式のExamplesにも挙げられている通り、Denoでは標準でfetch()を使えます。
HTTPリクエストを簡単に行うことが出来て便利です。

https://deno.land/manual@v1.11.2/examples/fetch_data

しかし、Fetchはちょっと複雑な問い合わせをしようとするとオプションがごちゃごちゃ増えやすいです。
そこで、今回はFetchの代わりになりそうなKyというライブラリを試してみます。

作業記録(Zenn scrap)はこちら

Kyとは

Fetchをベースに使いやすくしたnpmライブラリです。
Denoにも対応しているということで、今回白羽の矢が立ちました。

https://github.com/sindresorhus/ky

READMEのトップ画像が印象的です。

_人人人人人人人人人人人人_
> 極楽要求(しなさい) <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

うーん、「Superdry 極度乾燥(しなさい)」っぽいです。Delightful HTTP Requestを訳したらこうなったのでしょう。かなり癖の強い翻訳ソフトがあるようです。

また、kyの意味についてもREADMEの下の方に書いてあります。

What does ky mean?
It's just a random short npm package name I managed to get. It does, however, have a meaning in Japanese:

A form of text-able slang, KY is an abbreviation for 空気読めない (kuuki yomenai), which literally translates into “cannot read the air.” It's a phrase applied to someone who misses the implied meaning.

意訳:「npmパッケージ用に適当な短い名前をつけたんだけど、日本語で『空気読めない』の略だったんだ」

それで(無理矢理感のある)日本語訳が載っているのでしょうか。
あと、画像が桜だったり、リポジトリの説明に 🌳 がついていたりしているのは、「Ky」→「キィ」→「木」というのも意識しているのかもしれません。
本体の機能と関係ないところでツッコミどころが多すぎるライブラリです。

Fetchとの違い

  • HTTPメソッドを{method:"POST"}のようにオプションで指定するのではなくky.post()のようにメソッドとして指定できる
  • bodyに合わせたContent-Typeヘッダーを自動的につけてくれる
  • response.text()response.json()ky.get().json()のようにメソッドチェーンで書ける
  • その他便利なメソッドが定義されていたりいろんなオプションを使えたりする

といった感じのようです。いくつか試してみました。

GETリクエストを試す

とりあえず単純にGETリクエストをやってみます。fetch()と比較してみます。

fetch.ts
const response = await fetch("https://example.com/");
const text = await response.text();
console.log(text);
ky.ts
import ky from 'https://cdn.skypack.dev/ky?dts';
const text = await ky("https://example.com/").text();
console.log(text);

ここではky()を使っていますが、ky.get()でも同じ動きとなります。
まあ単純なGETだとFetchを使っても十分シンプルなので、それほど旨味がありませんね。

POSTリクエストを試す

LINE通知してみる

LINE通知したときに作ったスクリプトを書き換えてみます。
https://zenn.dev/kawarimidoll/articles/2937f4da6d9fa8

+ import { ky } from "./deps.ts";
import { LINE_ACCESS_TOKEN } from "./env.ts";
import { Logger } from "./logger.ts";
const url = "https://notify-api.line.me/api/notify";

const body = new URLSearchParams({
-   message: "hello from deno!",
+   message: "hello from ky!",
});

- const res = await fetch(url, {
-   method: "POST",
+ const json = await ky.post(url, {
  headers: {
    Authorization: `Bearer ${LINE_ACCESS_TOKEN}`,
-    "Content-Type": "application/x-www-form-urlencoded",
  },
  body,
- });
+ }).json();

- const json = await res.json()
Logger.info(json);

headersオプションに明確な違いが出ました。
Kyはbodyの型に合わせ、自動的にContent-Typeヘッダーを追加してくれます。型がURLSearchParamsの場合はapplication/x-www-form-urlencodedとなります。
おまじない的な文字列が消え、コードがスッキリしました。

Tweetしてみる

IFTTTとTwitterを連携させたときの例も試してみます。
https://zenn.dev/kawarimidoll/articles/234096fed2ee3c

IFTTTのレスポンスはjson()ではなくtext()で受け取る必要があります。

import { ky } from "./deps.ts";
import { IFTTT_WEBHOOK_KEY } from "./env.ts";
import { Logger } from "./logger.ts";
const url = "https://maker.ifttt.com/trigger/send_tweet/with/key/";

const json = await ky.post(`${url}${IFTTT_WEBHOOK_KEY}`, {
  json: { value1: "Denoからツイート" },
}).text();
Logger.info(json);

かなりシンプルに書けました。
Kyではbodyにjsonを入れてリクエストする場合、jsonオプションが使えます。
これにより、"Content-Type"の指定だけでなく、JSON.stringify()の変換も不要になります。

- header: { "Content-Type": "application/json" },
- bodyr: JSON.stringify({ value1: "Denoからツイート" }),
+ json: { value1: "Denoからツイート" },

さらにIFTTTのエンドポイントは末尾にwebhook_keyが付加される形なので、prefixUrlを使って文字列連結を省いてみます。
本来想定された用途とは違いそうな気もしますが、せっかくなので使ってみました。

import { ky } from "./deps.ts";
import { IFTTT_WEBHOOK_KEY } from "./env.ts";
import { Logger } from "./logger.ts";
- const url = "https://maker.ifttt.com/trigger/send_tweet/with/key/";
+ const prefixUrl = "https://maker.ifttt.com/trigger/send_tweet/with/key/";

- const json = await ky.post(`${url}${IFTTT_WEBHOOK_KEY}`, {
+ const json = await ky.post(IFTTT_WEBHOOK_KEY, {
+   prefixUrl,
  json: { value1: "Denoからツイート" },
}).text();
Logger.info(json);

そしてこちらのissueを参考にエラーハンドリングもつけました。このようになります。

import { ky } from "./deps.ts";
import { IFTTT_WEBHOOK_KEY } from "./env.ts";
import { Logger } from "./logger.ts";
const prefixUrl = "https://maker.ifttt.com/trigger/send_tweet/with/key/";

+ try {
  const json = await ky.post(IFTTT_WEBHOOK_KEY, {
      prefixUrl,
      json: { value1: "Denoからツイート" },
    }).text();
  Logger.info(json);
+ } catch (error) {
+   Logger.error(await error.response.text());
+ }

良いですね。これはなかなか極楽要求感があります。

おわりに

POSTリクエスト時のオプションをよしなにやってくれる点がかなり強力・有用に感じました。
実のところ、Fetchにそれほど不満を持っていたわけではないのですが、Kyを使ってみると「Fetchはおまじない的な記述が多いな…」と感じてしまいます。
ここで挙げた以外にも、リクエスト・リトライ・レスポンス時にhookを噛ませたり、ky.create()ky.extends()で各種設定を固定した状態のkyインスタンスを生成したりできるようです。
今後も積極的にKyを使い、発展的な機能も活用していこうと思います。

みんなもレッツ 極楽要求(しなさい)

denoでいろいろ試しているリポジトリはこちらです。
https://github.com/kawarimidoll/deno-dev-playground

Discussion