breakできるforEach ―throwを使って―

思いつき。break文のように入れ子になった内側のループの中から外側のループも抜けられるように。

function Break(target) {
    this.target = target;
}

function forEach(array, callbackfn, thisArg) {
    try {
        Array.prototype.forEach.call(array, callbackfn, thisArg);
    } catch (e) {
        if (!(e instanceof Break) || (e.target && e.target !== callbackfn)) {
            throw e;
        }
    }
}

使用例は以下の通り。

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9];

forEach(array, function (n) {
    console.log(n);
    if (n === 5) {
        // 引数を指定しなければ直近のforEachが止まる
        throw new Break();
    }
});

forEach(array, function outer(i) {
    forEach(array, function inner(j) {
        console.log('%d * %d = %d', i, j, i * j);
        if (i === 5 && j === 5) {
            // 引数を指定するとその関数をコールバックとして渡したforEachが止まる
            throw new Break(outer);
        }
    });
});

戻り値 === false で判断する方法は、reduceやfilterのような戻り値が意味を持つメソッドには使えない。一方、このthrow文の方法なら区別できる。でも、やっぱりここでthrow文は変な感じ。

someをforEach代わりに使うのも変な感じがする(と言いつつよくやるけど)。mapがあればforEachいらないよね、戻り値を無視すればいいだけなんだから、みたいな。

いっそのこと……

Array.prototype.forEach = Array.prototype.some;


Underscore.js、breaker変数をpublicにすればいいのに。