ES2015以前
javascriptにはオーバーロード機能が本来ありませんが、arguments
で引数を呼び出すことができる場合には次のようにすることで擬似的なオーバーロードが実現できるとされていました。
function overloading(arg1, arg2) { switch (arguments.length) { case 2: return funcFor2arguments(arg1, arg2); case 1: return funcFor1argument(arg1); default: throw "inappropriate argument number!"; } } function funcFor2arguments(arg1, arg2) { /* 2引数の場合の処理 */ } function funcFor1arguments(arg1) { /* 1引数の場合の処理 */ }
可変長引数の利用
ところで、ES2015では可変長引数をとる関数が定義できますので、arguments
という黒魔術を利用しなくても以下のようにしてオーバーロードもどきが実現可能です。 arguments
は例えばアロー関数の中などでは利用できないので、こちらのほうがより安全です。
function overloading(...args) { switch (args.length) { case 2: return funcFor2arguments(arg[0], arg[1]); case 1: return funcFor1argument(arg[0]); default: throw "inappropriate argument number!"; } } function funcFor2arguments(arg1, arg2) { /* 2引数の場合の処理 */ } function funcFor1arguments(arg1) { /* 1引数の場合の処理 */ }
この方法を用いて先日のコードを修正すると、次のようになります。
function call(main, ...args) { const obj = originalCall(main, ...args); obj.withTimeout = (...args) => { const [time, after] = args; // 配列argsの0番要素と1番要素をそれぞれtime, afterにバインド if (!after) { // 引数が1つしか渡されていない場合、1番要素に対応するafterはundefinedになる return race({ result: obj, isTimeout: call(delay, time) }); } else { return race({ result: obj, isTimeout: delay(time).then(after) }); } }; return obj; }
わりと読みやすくなった気がします。
おまけ
ここまで書いたところでjavascriptの柔軟性に思いを致し、「もしや・・・」と以下のように書いてみたらこれでも通りました。
function call(main, ...args) { const obj = originalCall(main, ...args); obj.withTimeout = (time, after) => { if (!after) { return race({ result: obj, isTimeout: call(delay, time) }); } else { return race({ result: obj, isTimeout: delay(time).then(after) }); } }; return obj; }
適切な書き方なのかはよくわかりませんが、単純なオーバーロードならこれでも十分っぽそうです。
(強いて言えば、第二引数にundefined
やfalseが渡されると二引数にもかかわらず一引数の場合の処理が呼び出されることになりそうですが、むしろ望ましい動作のような気もします)