LoginSignup
104
108

More than 5 years have passed since last update.

Deferredな $.ajax をラップしてコールバック関数(done/fail)に共通処理を追加する

Last updated at Posted at 2014-10-23

Deferred でない場合

コールバック処理を従来の success, error, complete で指定する方法。
こんな感じでプロジェクト特有の共通処理などを追加していることが多いと思います。

実装


var myAjax = function(arg) {
    var opt = $.extend({}, $.ajaxSettings, arg);

    opt.success = (function(func) {
        return function(data, statusText, jqXHR) {
            console.log('success時の共通処理 ...');

            if (func) {
                func(data, statusText, jqXHR);
            }
        };
    })(opt.success);

    opt.error = (function(func) {
        return function(jqXHR, statusText, errorThrown) {
            console.log('error時の共通処理 ...');

            if (func) {
                func(jqXHR, statusText, errorThrown);
            }
        };
    })(opt.error);

    opt.complete = (function(func) {
        return function(jqXHR, statusText) {
            console.log('complete時の共通処理 ...');

            if (func) {
                func(jqXHR, statusText);
            }
        };
    })(opt.complete);

    return $.ajax(opt);
};

使い方

myAjax({
    url: '/hoge',
    type: 'GET',
    dataType: 'json',
    data: {
        param1: 'value1',
        param2: 'value2'
    },
    success: function(json, statusText, jqXHR) {
        console.log('SUCCESS!');
    },
    error: function(jqXHR, statusText, errorThrown) {
        console.log('ERROR!');
    },
    complete: function(jqXHR, statusText) {
        console.log('COMPLETE!');
    }
});

実行結果

success時の共通処理 ...
SUCCESS!
complete時の共通処理 ...
COMPLETE!

Deferred な場合

コールバック処理をPromiseの done, fail, always で指定する方法。

実装

  • ポイント
    • jQuery1.8以降、$.ajax の結果(jqXHR)はXMLHttpRequestのプロパティ・メソッドに加えて、Promiseのプロパティ・メソッドも実装している
    • return 時、(myAjax内で生成した) DeferredオブジェクトをPromise化( defer.promise() )し、外部からは then()、done()、fail() といったメソッド以外、Deferredオブジェクトを操作出来ないようにする
    • return 時、(myAjax内で生成した) Deferredオブジェクトに $.ajax の結果(jqXHR) をマージすることで、元々のjqXHRのプロパティ・メソッドを使用できるのはそのままに、外部から処理(done、fail)を注入できるようにするのがミソ
var myAjax = function(arg) {
    var opt = $.extend({}, $.ajaxSettings, arg);
    var jqXHR = $.ajax(opt);

    var defer = $.Deferred();

    jqXHR.done(function(data, statusText, jqXHR) {
        console.log('done(=success)時の共通処理 ...');

        // defer.resovle ではなくて defer.resolveWith で
        // myAjax(...).done() 内でのthisのコンテキストを
        // 明示的に指定する
        defer.resolveWith(this, arguments);
    });

    jqXHR.fail(function(jqXHR, statusText, errorThrown) {
        console.log('fail(=error)時の共通処理 ...');

        // defer.reject ではなくて defer.rejectWith で
        // myAjax(...).fail() 内でのthisのコンテキストを
        // 明示的に指定する
        defer.rejectWith(this, arguments);
    });

    jqXHR.always(function() {
        console.log('always(=complete)時の共通処理 ...');
    });

    return $.extend({}, jqXHR, defer.promise());
};

使い方

myAjax({
    url: '/hoge',
    type: 'GET',
    dataType: 'json',
    data: {
        param1: 'value1',
        param2: 'value2'
    }
}).done(function(json, statusText, jqXHR) {
    console.log('DONE!');
}).fail(function(jqXHR, statusText, errorThrown) {
    console.log('FAIL!');
}).always(function() {
    console.log('ALWAYS!');
});

実行結果

done(=success)時の共通処理 ...
DONE!
always(=complete)時の共通処理 ...
ALWAYS!

ハマったポイント

① ラッパー内でDeferredオブジェクトを生成しない場合

つまり、

var myAjax = function(arg) {
    var opt = $.extend({}, $.ajaxSettings, arg);
    var jqXHR = $.ajax(opt);

    jqXHR.done(function(data, statusText, jqXHR) {
        console.log('done(=success)時の共通処理 ...');
    });

    jqXHR.always(function() {
        console.log('always(=complete)時の共通処理 ...');
    });

    return jqXHR;
};

myAjax({
// ...
}).done(function(json, statusText, jqXHR) {
    console.log('DONE!');
}).always(function() {
    console.log('ALWAYS!');
});

だとどうなるか?
こうなる。

done(=success)時の共通処理 ...
DONE!
always(=complete)時の共通処理 ...
ALWAYS!

え?いいんじゃないの?
でも、こんな時ダメ

var myAjax = function(arg) {
    var opt = $.extend({}, $.ajaxSettings, arg);
    var jqXHR = $.ajax(opt);

    jqXHR.done(function(data, statusText, jqXHR) {
        if (data.isXXX) {
            console.log('何かをハンドリングして、この場合はここでdoneを終わりたい!');
            return;
        }
    });

    jqXHR.always(function() {
        console.log('always(=complete)時の共通処理 ...');
    });

    return jqXHR;
};

結果。

何かをハンドリングして、この場合はここでdoneを終わりたい!
DONE!  ←★この処理は実行したくない★
always(=complete)時の共通処理 ...
ALWAYS!

done/failの共通処理内でdone/failを切り上げられない。
これはこう書いたのと同じ

$.ajax({
// ...
}).done(function() {
    if (data.isXXX) {
        console.log('何かをハンドリングして、この場合はここでdoneを終わりたい!');
        return;
    }
}).done(function() {
    console.log('DONE!');
})...

2つのdoneが別々のコールバック処理としてイベントキューに入れられて順番(=FIFO)に実行されるため。

② ラッパーがreturnするDeferred(Promise)オブジェクトのmerge順が逆

もし、

var myAjax = function(arg) {
    var opt = $.extend({}, $.ajaxSettings, arg);
    var jqXHR = $.ajax(opt);

    var defer = $.Deferred();

// ...

    // return $.extend({}, jqXHR, defer.promise());
    return $.extend({}, defer.promise(), jqXHR);
};

とすると、deferのdoneやfailがjqXHRのそれらで上書きされてしまうため、①と同じ結果になる。

③ ラッパーのdone/failのコールバック関数内で参照するthisのコンテキスト

下記のようにdefer.resolveでコールバック関数(done)を実行できるようにして何の問題もなさそうだけど...

var myAjax = function(arg) {
    var opt = $.extend({}, $.ajaxSettings, arg);
    var jqXHR = $.ajax(opt);

    var defer = $.Deferred();

    jqXHR.done(function(data, statusText, jqXHR) {
        console.log('done(=success)時の共通処理 ...');

        // ★ ↓↓↓ ★
        defer.resolve(data, statusText, jqXHR); 
        // ★ ↑↑↑ ★
    });

// ...

    return $.extend({}, jqXHR, defer.promise());
};

myAjax({
// ...
}).done(function() {
        // ↓ このthisは何者??
    console.log(this);
})...

myAjax実行時のdoneのコールバック内のthisを見てみると
myAjax内で生成したDeferred(Promise)オブジェクト相当になっている。

Pentan 2.png

そこで下記のようにdefer.resolveWithでコールバック関数内でのthisのコンテキストを明示的に指定してあげると...

var myAjax = function(arg) {
    var opt = $.extend({}, $.ajaxSettings, arg);
    var jqXHR = $.ajax(opt);

    var defer = $.Deferred();

    jqXHR.done(function(data, statusText, jqXHR) {
        console.log('done(=success)時の共通処理 ...');

        // ★ ↓↓↓ ★
        defer.resolveWith(this, arguments);
        // ★ ↑↑↑ ★
    });

// ...

    return $.extend({}, jqXHR, defer.promise());
};

myAjax({
// ...
}).done(function() {
        // ↓ このthisは何者??
    console.log(this);
})...

バッチリ元々の$.ajaxのthis相当に👍

Pentan.png

104
108
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
104
108