jQuery の Deferred でのエラー処理 その(2)

この内容は jQuery 1.7.2 を元に記述しています。

jQuery の Deferred でのエラー処理 その(1)」では基本的なエラー処理をみて、「jQuery の Deferred の then について」では then がただ単純にコールバック関数を登録してるだけってのが確認できました。

ここでは、deferred を繋げて実行する pipe でのエラー処理を確認してみます。

基本的な使い方は以下のような感じ。

async("Test 1", 1000).pipe(function(e){
    return async("Test 2", 500);
}).pipe(function(e){
    return async("Test 3", 1000);
}).done(function(e){
    console.log("done - " + e);
});

結果は以下。

f:id:k_maru:20120623202013p:plain

この時点で個人的には頭を抱えてしまいそうなんですが、最後の結果だけが返ってきてるようです。先の2つの結果はどこえ消えたのか・・・。もちろん、引数が複数になっているわけではありません。仮に複数になってても第1引数になぜに最後の結果なのか、こっちも頭を抱えてしまいますが。。

調べてみると、どうやらそれぞれの結果は pipe に登録するコールバック関数の引数に渡ってくるようです。

async("Test 1", 1000).pipe(function(e){
    console.log("pipe - " + e);
    return async("Test 2", 500);
}).pipe(function(e){
    console.log("pipe - " + e);
    return async("Test 3", 1000);
}).done(function(e){
    console.log("done - " + e);
});

f:id:k_maru:20120623202025p:plain

まぁ、これはこれで分かるのですが、単純に pipe は「次の処理を行う」のではなく、「結果を渡した際に、次の処理を依頼(deferred/promiseが返却)されたら実行する」というような、動きのようです。

ここからエラーの際の動きを確かめてみますが、上記まででなんとなくどういう動きをするのかは予想がつきますね。

async("Test 1", 1000).pipe(function(e){
    /* ここで reject */
    return async("Test 2", 500, true);
}).pipe(function(e){
    return async("Test 3", 1000);
}).done(function(e){
    console.log("done - " + e);
}).fail(function(e){
    console.log("fail - " + e);
});

f:id:k_maru:20120623203117p:plain

2つ目(Test 2)で reject してるのですが、後続する pipe にエラー時のコールバックが登録されていないため、そのままなにも行われず、 fail のコールバックが実行されています。ここで、間違ってはいけないのは「エラーが起こったら、後続する pipe が飛ばされる」のではないってところですかね。ちょっと確認してみます。

/* 先頭で reject */
async("Test 1", 1000, true).pipe(function(e){
    return async("Test 2", 500);
}, function(e){
    console.log("errorback - Test2 " + e);
}).pipe(function(e){
    return async("Test 3", 1000);
}, function(e){
    console.log("errorback - Test3 " + e);
}).done(function(e){
    console.log("done - " + e);
}).fail(function(e){
    console.log("fail - " + e);
});

f:id:k_maru:20120623203947p:plain

後続する pipe にエラー時のコールバックが指定されている場合は、ちゃんと実行されていますね。ただ、さらによく見てみると2つ目以降の以降のコールバックの値が undefined になってます。これは1つ目で値を返してないからですね。ちゃんと reject で値を指定して返さないといけないようです。

/* 先頭で reject */
async("Test 1", 1000, true).pipe(function(e){
    return async("Test 2", 500);
}, function(e){
    console.log("errorback - Test2 " + e);
    return $.Deferred().reject(e);
}).pipe(function(e){
    return async("Test 3", 1000);
}, function(e){
    console.log("errorback - Test3 " + e);
    return $.Deferred().reject(e);
}).done(function(e){
    console.log("done - " + e);
}).fail(function(e){
    console.log("fail - " + e);
});

f:id:k_maru:20120623204643p:plain

もう、どれがどのコールバックなのか判別つきません。。ネストが深いのとどっちがうれしいんだか。

エラーのコールバックは登録せずに fail に任せればいいのかもしれません。が、それだと、今回の場合みたいに渡される引数の値でどの処理なのかが判別できない場合、どこでエラーが発生したのか判別できません。なので、そういう場合はやはりエラーのコールバックはそれぞれに登録する必要がありそうです。さらにそうした場合、手前で発生したエラーなのか、さらにそれ以前で発生したのかを判別する必要も出てきそうです。さらにさらに、エラーが発生した前までの結果データをもとになにか行いたい場合(補償トランザクションとか画面の再描画とか)には、結果データの保持も必要そうです。ちょっと書いてみました。

var results = [];
/* 先頭で reject */
async("", 1000, true).pipe(function(e){
    results.push(e);
    return async("", 500);
}, function(e){
    return $.Deferred().reject({ errorpos: 1, data: e });
}).pipe(function(e){
    results.push(e);
    return async("", 1000);
}, function(e){
    var result = e;
    if(!e || !e.errorpos || e.errorpos < 1){
        result = { errorpos: 2, data: e };
    }
    return $.Deferred().reject(result);
}).done(function(e){
    var i = 0, length = results.length;
    results.push(e);
    for(; i < length; i++){
        render(results[i]); //何かの処理
    }
}).fail(function(e){
    var i = 0, length = results.length;
    showerror((e.errorpos && e.data) ? e.data : e);
    for(; i < length; i++){
        revert(results[i]); //何かの処理
    }       
});

イメージコードなので、あってるかどうか確証はないですが、結構大変そうです。

ということで、pipe での嬉しさがでるのは「エラーが起こっても、単純に起こったことが取れれば OK で、どこで起こったのか、起こるまでの結果は不要」なときくらいでしょうか。もうちょっとうまいやり方(あくまでも jQuery 標準の API を使って)あってもいいのかなと思うのですが、無いんでしょうか。。。

jQuery の Deferred の then について

この内容は jQuery 1.7.2 を元に記述しています。

jQuery の Deferred でのエラー処理 その(1)」で

Deferred のインターフェイス的にはなんとなく、 then で繋いでいって、 done / fail で最後処理をするように見受けられます。

って書いてましたが、よくよく調べるとまったくそんなことは無かったです。 then はただ単純に deferred が resolve / reject / notify された時に呼び出されるコールバック関数をキューに登録するだけでした。done / fail / notify の各関数も同じでした。

ってことで、こんなコードを書いても、上から順番に関数が呼び出されるだけです。

async("Test", 1000).then(function(e){
    console.log("then 1");
}).then(function(e){
    console.log("then 2");
}).done(function(e){
    console.log("done 1");
}).then(function(e){
    console.log("then 3");
}).done(function(e){
    console.log("done 2");
});

結果は以下。

f:id:k_maru:20120623193934p:plain

deferred 単品で(pipeとかwhenとか無しに)使うことなんて無いんじゃないかって結果ですね。
ただ、さっき GitHub から開発中の jQuery のソースをとってきたら、 then に現在の pipe 相当のコードが記述されて、 pipe は then のシンタックスシュガーになってました。次のバージョンからは then で処理をつなげられるようになるのかもしれません。

jQuery の Deferred でのエラー処理 その(1)

この内容は jQuery 1.7.2 を元に記述しています。

jQuery の Deferred の使い方はそこら中にでてるのですが、詳細なエラー処理の動きについてはあまり見つからないので調べてみました。ってことで、複数回に分けて書いてみます。

とりあえず、非同期処理のファンクションとして以下のようなのを使ってます。第3引数に true を設定すると、reject でマークされるようにしてます。

var async = function(value, timeout, reject){
    var deferred = $.Deferred();
    console.log("start - " + value);
    setTimeout(function(){
        console.log("end - " + value);
        if(reject){
            deferred.reject(value);
        }else{
            deferred.resolve(value);
        }

    }, timeout);
    return deferred.promise();
};

reject された場合は、通常、 then の第2引数または fail にコールバックを設定することで取得できます。

async("test", 1000, true).then(null, function(e){
   console.log("then - " + e);
}).fail(function(e){
    console.log("fail - " + e);
});

結果は以下のような感じ。

f:id:k_maru:20120623161326p:plain

基本はこれですが、例えば、複数のデータを取得したくてサービスコールしたら、サービスの動きがイケてなくて 0 件の場合に 404 とかを返されるとかありますよね。でも、アプリケーション的には 404 のエラーではなくって 0 件のデータとして扱いたい。

ってことで、 reject されたときでも、内容によっては resolve にしたいことがあります。

Deferred のインターフェイス的にはなんとなく、 then で繋いでいって、 done / fail で最後処理をするように見受けられます。ってことで、 then の時に、 resolve でマークされた Deferred を返せば done が実行されるとうれしいです。

async("test", 1000, true).then(null, function(e){
    console.log("then - " + e);
    if(true /*本当はなにかの判定処理*/){
        return $.Deferred().resolve(e);
    }
}).done(function(e){
    console.log("done - " + e);
}).fail(function(e){
    console.log("fail - " + e);
});

これで done が実行されれば嬉しかったのですが、そうではなかったです。

f:id:k_maru:20120623163213p:plain

どうやら then では、書き換えられないようですね。なんか、 promise を繋いでいくのではなく、 promise の結果を取得して、処理を行う口になっているだけみたいです。どうもインターフェイスのイメージに合わないです。

で、これを実現するためにはどうするかを探したら pipe ってのがありました。どうやらこれが繋いでいく関数のようです。なんか、 then ってなんなのよってなりそうです。

// then じゃなくて pipe
async("test", 1000, true).pipe(null, function(e){
    console.log("then - " + e);
    if(true /*本当はなにかの判定処理*/){
        return $.Deferred().resolve(e);
    }
}).done(function(e){
    console.log("done - " + e);
}).fail(function(e){
    console.log("fail - " + e);
});

結果は期待通りです。

f:id:k_maru:20120623164304p:plain

さてさて、基本的なところはここまでですが、本来は複数の非同期処理をまとめて行いたいときに、できるだけネストをなくしてストレートに書いて結果を取りたいってのが Deferred の強みのはずなので、次回以降は pipe でのシーケンシャル実行の際のエラー処理と、 when でのパラレル実行の際のエラー処理を書きたいと思います。

WebStorm で IIS Express

最近 WebStorm を使う機会が多くなってます。
VSなら組み込みで Web サーバーを持っていて、立ち上がってくれるのですが、 WebStorm は持ってないのでちょっとしたコードを書きたいときに面倒だったりします。そこで WebStorm から IIS Express を起動できるように設定してみました。ってことで、その設定手順です。

[File] メニューから [Settings] を選択します。
f:id:k_maru:20120616120919p:plain

[Settings] ダイアログの [IDE Settings] 内にある [External Tools] を選択して、右ペインの上のほうにある [+] ボタンをクリックします。
f:id:k_maru:20120616120935p:plain

表示された [Edit Tool] ダイアログを設定します。必要なのは以下。

  • [Program]:iisexpress.exe を選択します。
  • [Parameters]:引数をしていします。引数のパスはプロジェクトのルートディレクトリが設定されるように、macro で "$FileDir$" を選択します。ポートはお好きなのをどうぞ。

f:id:k_maru:20120616120948p:plain

これで、メニューに指定した内容が表示されるようになります。
f:id:k_maru:20120616120958p:plain

もちろん、選択すると IIS Express が起動します。ちょっと文字化けしますけどね・・。
f:id:k_maru:20120616121217p:plain

これで、ちょっとしたものを書いて試したいときでも、いつでもサーバー上で動かせますね!ファイルを直接ブラウザで開いたときって、JavaScript のセキュリティ周りの挙動が違うことがあるのですが、もう安心です。

Windows 8 でのプレゼン時にタッチポイントを表示する方法

この内容は Windows 8 Consumer Preview を元に記述しています。

先日行われたWWDとかでデモとかを見ているとタッチしている指の位置が画面に表示されていて、どうやって出すのかなと思って探してみたらコンパネにありました。

コンパネにある[ペンとタッチ]を選択して、表示されたダイアログで[外部モニターでの表示に視覚的フィードバックを最適化する]を選択するだけです。

f:id:k_maru:20120503235548p:plain

以下はスタート画面で2本指でピンチしているところです。

f:id:k_maru:20120503235910p:plain

スタート画面の背景色を変更したときに変更される選択色の色コード

この内容は Windows 8 Consumer Preview を元に記述しています。

Windows 8 でも Windows Phone 7 みたいにアクセントカラーが決められるのかなと思ってたのですが、いまだ MSDN にも影も形も見えないので、ひょっとしたらアクセントカラーって決められないのかなと思い始めてる今日この頃です。

でも、やっぱり指標は欲しいということで、スタート画面の背景色を変更したときに同時に選択色も変更されるので、その色コードを調べてみました。

イメージ 色コード
f:id:k_maru:20120307224601p:plain:w200 R=0, G=130, B=135 (#008287)
f:id:k_maru:20120307225009p:plain:w200 R=1, G=157, B=195 (#019DC3)
f:id:k_maru:20120307225458p:plain:w200 R=0, G=105, B=192 (#0069C0)
f:id:k_maru:20120307225605p:plain:w200 R=114, G=0, B=172 (#7200AC)
f:id:k_maru:20120307225702p:plain:w200 R=194, G=1, B=106 (#C2016A)
f:id:k_maru:20120307225749p:plain:w200 R=191, G=31, B=0 (#BF1F00)
f:id:k_maru:20120307225840p:plain:w200 R=130, G=56, B=0 (#823800)
f:id:k_maru:20120307225920p:plain:w200 R=26, G=154, B=1 (#1A9A01)
f:id:k_maru:20120307225954p:plain:w200 R=255, G=125, B=35 (#FF7D23)


デフォルトでインストールされてる、メールとかストアとかマップとかのタイルの色とか選択色とかとは違うのが気になるところではあるのですが。