📚

SvelteKitでサーバーサイドのPromiseをクライアントサイドで解決し、エラーとなった際になぜかInternal Errorになる

に公開

みなさま大コーディングエージェント時代いかがお過ごしでしょうか?
(最近はみんなツールの使い方の話しか投稿してなくて一抹の寂しさを感じますね・・・)

SvelteKitのawaitブロックを使った遅延読み込み処理で詰まったことがあったのに備忘録として残しておきます。よく読めばドキュメントに書いてあることなのですが、同じく詰まる方が減ったら良いな

結論

サーバーサイドから返すPromise型をクライアントサイドで解決する場合、かつ、Promiseのエラーメッセージを表示させたい場合には、標準のErrorオブジェクトではなく、@sveltejs/kiterrorオブジェクトを使いましょう。
※クライアントサイドのPromiseであれば、Errorでも問題ないです。
https://svelte.jp/docs/kit/errors

事象

+page.server.tsのload関数で返しているPromiseをクライアントサイドで解決した際に、reject時のエラーメッセージが「Internal Error」となってしまいました。Promiseのエラーメッセージがそのまま表示されることを想定していました。
※awaitブロックを使っている時に発生したので、それが原因かと思いましたが関係ありませんでした。

試してみる

サーバーサイド、クライアントサイドのプロミスそれぞれで動作を確認してみます。

サーバーサイドのPromise

公式のチュートリアルのコードのjsをサーバー側で実行してみます。
https://svelte.jp/tutorial/svelte/await-blocks

rejectされるとErrorオブジェクトを返します。

+page.server.ts
export const load = async () => {
  const testPromise = new Promise((fulfil, reject) => {
    setTimeout(() => {
      // simulate a flaky network
      if (Math.random() < 1) { // エラーにしたいのでrejectしか発生しないようにします。
        reject(new Error('Request failed'));
        return;
      }

      fulfil(Math.floor(Math.random() * 6) + 1);
    }, 1000);
  })
  return {
    testPromise
  }
}
+page.svelte
<script lang="ts">
  let { data } = $props()
</script>

{#await data.testPromise}
  読み込み中です。
{:then value}
  {value}
{:catch error}
  エラーが発生しました: {error.message}
{/await}

Internal Errorが表示されました。

クライアントサイドのPromise

+page.svelte
<script lang="ts">
  const testPromise = new Promise((fulfil, reject) => {
    setTimeout(() => {
      // simulate a flaky network
      if (Math.random() < 1) { // エラーにしたいのでrejectしか発生しないようにします。
        reject(new Error('Request failed'));
        return;
      }

      fulfil(Math.floor(Math.random() * 6) + 1);
    }, 1000);
  })
</script>

{#await data.testPromise}
  読み込み中です。
{:then value}
  {value}
{:catch error}
  エラーが発生しました: {error.message}
{/await}

Request failedが表示されました。

なぜInternal Errorになってしまうのか

SvelteKitでは、想定されるエラーと予期せぬエラーが区別されます。
https://svelte.jp/docs/kit/errors#Error-objects

想定されるエラーとは、@sveltejs/kit からインポートされる error を使用して作成されるものを指します

予期せぬエラーとは、リクエストの処理中に発生するその他の例外のことを指します。これらは機密情報を含むことがあるため、予期せぬエラーのメッセージとスタックトレースはユーザーには公開されません。

サーバーサイドのエラーをそのまま出してしまうと予期せぬ情報を出してしまう可能性があるから、明示的にerrorで宣言されたものだけが表示できるようになっているようです。
私はAxiosのリクエストのPromiseを使っていたので、予期せぬエラーとなっている方が安心して使えそうですね。特定のメッセージを表示させたい場合には、errorで返しましょう。

なおクライアントサイドだけの場合は、SvelteKitの世界の話ではないので正しくメッセージが表示されるものと認識しています。

どう修正すれば良いか

先ほどのPromiseのcatchでerrorをthrowすれば表示できるようになります。

+page.server.ts
import { error } from "@sveltejs/kit";

export const load = async () => {
  const testPromise = new Promise((fulfil, reject) => {
    setTimeout(() => {
      // simulate a flaky network
      if (Math.random() < 1) { // エラーにしたいのでrejectしか発生しないようにします。
        reject(new Error('Request failed'));
        return;
      }

      fulfil(Math.floor(Math.random() * 6) + 1);
    }, 1000);
  }).catch((rejectError) => {
    error(400, {message: rejectError.message});
  })
  return {
    testPromise
  }
}
+page.svelte
<script lang="ts">
  let { data } = $props()
</script>

{#await data.testPromise}
  読み込み中です。
{:then value}
  {value}
{:catch error}
  エラーが発生しました: {error.message}
{/await}

さいごに

本当はClaude Codeに解決させようとしましたが、筋の良い解決をしてくれなかったので自分で調べました。
コーディングエージェント時代ですが、エージェントの誤りを正せるように最低限の知識のキャッチアップは今後も必要だと感じました。

Discussion