包装 jQuery.ajax:将内部 jqXHR 作为新的延迟对象级联到 return

Wrapping jQuery.ajax: Cascading inner jqXHR as a new deffered object to return

我现在正在开发一个 jQuery 插件,我想在 $.ajax 发送之前做一些预处理操作:

// The signature is the same with $.ajax
$.myAjax = function(url, options) {


    var data = options.data;
    var promises = [];

    for(var name in data) {
        if(data.hasOwnProerty(name)) {
            var val = data[name];
            if(val instanceof File) {

                // I want to do some async pre-process here.
                var dfd = $.Deferred();

                var reader = new FileReader();
                reader.onload = function(e) {
                    data.name = e.target.result;
                    dfd.resolve();
                }
                reader.readAsText(val);

                promises.push(dfd.promise());
            }
        }
    }

    var deferred = $.Deferred();

    $.when.apply($, promises).done(function() {
        // In fact, I want to return, or wrap cascading this jqXHR
        //   in the outer function `$.myAjax`.
        var jqXHR = $.ajax(url, options).done(function(...) {
            // ??? If I want deferred to be a jqXHR like object, 
            // how to wrap the arguments here?
            deferred.resolve(/* Help to fill */); 
        }).fail(function(...) {
            deferred.reject(/* Help to fill */);
        });
    });

    // ** ATTENTION **
    // Here, I want to return a jqXHR compatible promise.
    // That is what I ask here.
    return deferred.promise();

}

我想 return myAjax 中的一个 Deferred 对象,或者更准确地说,一个 jqXHR 对象。

这样我就可以调用,与标准$.ajax方法完全相同的接口:

$.fn.myAjax({...}).done(function(data, textStatus, jqXHR) {
    // ...
}).fail(function(jqXHR, textStatus, errorThrown) {
    // ...
}) 
// .always ... etc.

如果我正确地理解了您要做什么,那是无法完成的。问题是你的代码 returns 来自 $.myAjax() 在你创建 jqXHR 对象之前,所以 jqXHR 对象不可能是来自 [=11] 的实际 return 对象=] 函数调用。您可以从 returned 承诺访问它,但 returned 承诺将是您在 ajax 调用甚至开始之前创建的承诺。

仅供参考,您的代码中也有一些 promise 反模式,因为您从 $.when() 处理程序 return $.ajax() 而不是使用您创建的另一个延迟。从 .then() 处理程序中返回承诺会自动将该承诺链接到原始承诺。


这是您发布的解决方案的清理版本。变更摘要:

  1. 将文件读取封装到本地函数中以避免在循环中声明函数并允许所有核心逻辑流仅使用承诺而不是承诺和回调的混合(例如封装回调)。
  2. 向文件添加了错误处理 reader
  3. 将 deferred 切换为回调模型(std promises 使用)
  4. 删除了 deferred anti-pattern 而只是 return ajax 承诺,它将根据需要从 ajax 调用中为您提供解决或拒绝的参数
  5. 切换到 .then(),它具有更标准的行为,当 jQuery 使他们的承诺符合标准时不需要更改

代码:

// The signature is the same with $.ajax
$.myAjax = function(url, options) {

    function readFile(data, name) {
        var file = data[name];
        if (file instanceof File) {
            return $.Deferred(function(dfd) {
                var reader = new FileReader();
                reader.onload = function(e) {
                    dfd.resolve(e.target.result);
                    data[name] = e.target.result;
                };
                reader.onerror = reader.onabort = dfd.reject;
                reader.readAsText(file);

            }).promise();
        }
    }

    var data = options.data;
    var promises = [];

    for(var name in data) {
        if(data.hasOwnProerty(name)) {
            promises.push(readFile(data, name));
        }
    }

    // trigger when all file fields was loaded.
    // so the data were all constructed.
    return $.when.apply($, promises).then(function() {
        return $.ajax(url, options);
    });
}

最后我自己的解决方法:

最后,我尝试制作一个延迟 resolveWithrejectWithjqXHR.done()jqXHR.fail() 签名相同的参数列表。

签名参考:http://api.jquery.com/jQuery.ajax/#jqXHR

jqXHR.done(function( data, textStatus, jqXHR ) {});

jqXHR.fail(function( jqXHR, textStatus, errorThrown ) {});

jqXHR.always(function( data|jqXHR, textStatus, jqXHR|errorThrown ) {});

所以整体的解决方案是:

// The signature is the same with $.ajax
$.myAjax = function(url, options) {

    var data = options.data;
    var promises = [];

    for(var name in data) {
        if(data.hasOwnProerty(name)) {
            var val = data[name];
            if(val instanceof File) {
                (function(name, val) {
                    // Deferred for a single field loaded.
                    var dfd = $.Deferred();
                    var reader = new FileReader();
                    reader.onload = function(e) {
                        data[name] = e.target.result;
                        dfd.resolve();
                    }
                    reader.readAsText(val);
                    promises.push(dfd.promise());
                })(name, val);
            }
        }
    }

    // Overall deferred to cascading jqXHR from ajax.
    // with returning the same argument list.
    var deferred = $.Deferred();

    // resolveWith or rejectWith requires a context.
    // Thought from the jQuery ajax source code.
    var callbackContext = options.context || options;

    // trigger when all file fields was loaded.
    // so the data were all constructed.
    $.when.apply($, promises).done(function() {

        // ********** FINAL SOLUTION **********
        $.ajax(url, options).done(
          function(data, textStatus, jqXHR) {
            deferred.resolveWith(context, 
                [data, textStatus, jqXHR]); 
        }).fail(
          function(jqXHR, textStatus, errorThrown) {
            deferred.rejectWith(context, 
                [jqXHR, textStatus, errorThrown]);
        });
    });

    // So that the resulting promise is well constructed.
    return deferred.promise();

}

所以,现在我们可以像 $.ajax 一样使用 $.myAjax 函数:

var dfd = $.myAjax({
    url: '...',
    // ...
}).done(function(data, textStatus, jqXHR) {
    // triggered when the inner ajax done ...
}).fail(function(jqXHR, textStatus, errorThrown) {
    // triggered when the inner ajax fail ...
});