Skip to content

エラーハンドリング

このページでは、eval/callFunction などが返す EvalError の詳細、ホスト側から JavaScript に例外を投げる方法、そして JavaScript 側の try/catch との関係について説明します。

EvalError

evalcallFunctionrunScriptcompileScriptevalModule はいずれも EvalError!Value を返します。

pub const EvalError = error{
    OutOfMemory,
    StackOverflow,
    ArityMismatch,
    CallStackOverflow,
    TryStackOverflow,
    UncaughtException,
    JumpTooLarge,
    NotCallable,
    RuntimeError,
};
エラー 意味
OutOfMemory メモリ確保に失敗した(initWithMemoryLimit の上限超過を含む)
StackOverflow 値スタック(式の評価スタック)があふれた
ArityMismatch 関数呼び出しの引数の数が想定と一致しない(内部的な不整合)
CallStackOverflow 関数呼び出しのネストが深すぎる(無限再帰など)
TryStackOverflow try/catch/finally のネストが深すぎる
UncaughtException JavaScript コード内で例外(throw)が捕捉されずに伝播した
JumpTooLarge バイトコードのジャンプオフセットが表現範囲を超えた(内部エラー)
NotCallable 呼び出し可能でない値を関数として呼び出そうとした(callFunction など)
RuntimeError 上記に分類されないランタイムエラー全般。requestInterrupt による中断もこれになります

もっとも頻繁に扱うのは UncaughtException です。JavaScript 側で例外が投げられ、どこにも catch されなかった場合にこの値が返ります。

const result = ctx.eval("throw new TypeError('bad')");
try std.testing.expectError(error.UncaughtException, result);

例外オブジェクトを取得する: getPendingException

EvalError はあくまで「例外が起きたかどうか」を示すだけで、実際に投げられた値(Error インスタンスや任意の値)は別途 getPendingException で取得します。

const result = ctx.eval("throw new TypeError('bad')");
if (result) |_| {} else |err| {
    if (err == error.UncaughtException) {
        const ex = ctx.getPendingException().?;
        const msg = try yanmajs.toString(ctx, ex);
        defer ctx.allocator.free(msg);
        // msg == "TypeError: bad"
    }
}
  • ctx.getPendingException() ?Value — 保留中の例外を返し、同時に内部状態をクリアします。2 回続けて呼ぶと 2 回目は null になります。

パターン: eval/callFunction などを呼んだ後は、戻り値をチェックして error.UncaughtException であれば getPendingException() で例外オブジェクトを取り出し、ログ出力やユーザーへのエラー表示に使う、という流れが基本形です。取得を忘れると次の例外発生時に上書きされてしまうので注意してください。

getPendingExceptioneval の失敗直後だけでなく、throwValue/throwXxxError を呼んだ直後にホスト側から直接呼び出すこともできます(下記参照)。

ホストから例外を投げる

ホスト関数(HostFn/HostMethod)の中、あるいは任意のタイミングで、Context の以下のメソッドを使って JavaScript 側に例外を投げられます。

ctx.throwValue(val: Value) void
ctx.throwError(name: []const u8, message: []const u8) void
ctx.throwTypeError(message: []const u8) void
ctx.throwRangeError(message: []const u8) void
ctx.throwReferenceError(message: []const u8) void
ctx.throwSyntaxError(message: []const u8) void
  • throwValue — 任意の Value をそのまま例外として投げます(Error インスタンスである必要はありません)。
  • throwError(name, message)name という名前のエラーコンストラクタ(TypeErrorRangeError など組み込みのものだけでなく、任意の文字列)でエラーオブジェクトを作って投げます。
  • throwTypeError/throwRangeError/throwReferenceError/throwSyntaxError — それぞれ対応する組み込みエラー型(throwError("TypeError", ...) 等)のショートカットです。
ctx.throwTypeError("expected a number");
const ex = ctx.getPendingException().?;
// toString(ctx, ex) は "TypeError: expected a number" を含む

これらのメソッドはいずれも 戻り値を持たない(void) ことに注意してください。呼び出しても Zig の制御フローはそのまま続くので、ホスト関数の中で使う場合は呼び出した後に明示的に return してください(ホスト関数とコールバック の「エラーの伝播」も参照)。

const validateFn: yanmajs.HostFn = struct {
    fn call(c: *Context, args: []const Value) !Value {
        if (args.len == 0 or !yanmajs.isNumber(args[0])) {
            c.throwTypeError("argument must be a number");
            return yanmajs.makeUndefined(); // throw の後も return は必須
        }
        return args[0];
    }
}.call;

ホスト関数の呼び出し元(JavaScript 側)から見ると、これは通常の throw と同じように振る舞い、eval/callFunctionerror.UncaughtException を返します。

Zig の error を返した場合の自動変換

HostFn/HostMethod が上記の throwXxx を使わず、単純に Zig の errorreturn した場合も、自動的に JavaScript の例外に変換されます。

fn call(c: *Context, args: []const Value) !Value {
    if (args.len == 0) return error.MissingArgument;
    // ...
}

この場合、message が Zig のエラー名(@errorName(err)、例では "MissingArgument")になった汎用の Error オブジェクトが生成されます。特定のエラー型(TypeError など)にしたい場合は、前述の throwXxxError を使ってください。

JavaScript 側の try/catch との関係

ホストから throwXxx/Zig の error のどちらの方法で例外を投げても、JavaScript コードから見れば通常の例外と変わりません。try/catch/finally で正しく捕捉できます。

try {
  validate('not a number');
} catch (e) {
  console.log(e instanceof TypeError, e.message);
  // true, "argument must be a number"
}

この場合、ホスト側の eval/callFunction は(JavaScript 内で catch されているので)例外を返さず、通常どおり結果の Value を返します。

JSON のエラー

parseJSON/stringifyJSON(内部的には JSON.parse/JSON.stringify を呼び出すヘルパー)でも同様に、失敗時は保留中の例外としてエラーが残ります。

const val = yanmajs.parseJSON(ctx, "{invalid json") catch |err| {
    if (err == error.UncaughtException) {
        const ex = ctx.getPendingException().?; // SyntaxError
        const msg = try yanmajs.toString(ctx, ex);
        defer ctx.allocator.free(msg);
    }
    return err;
};

parseJSON/stringifyJSON の詳細は Value の扱い方 を参照してください。

実行の中断も RuntimeError として扱われる

高度な機能 で説明する requestInterrupt() によってスクリプトの実行が中断された場合も、evalerror.RuntimeError を返し、getPendingException() で "interrupted" という文言を含むメッセージの例外オブジェクトを取得できます。

次のステップ