包装jQuery.ajax:级联内部jqXHR作为一个新的延迟对象返回

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

本文关键字:一个 返回 对象 延迟 ajax jQuery 级联 内部 jqXHR 包装      更新时间:2023-09-26

我现在正在开发一个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();
}

我想在myAjax中返回一个Deferred对象,或者更准确地说,一个jqXHR对象。

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

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

如果我正确理解了你想做的事情,那是不可能的。问题是,您的代码在创建jqXHR对象之前就从$.myAjax()返回,因此jqXHR对象不可能是$.myAjax()函数调用的实际返回对象。你可以从返回的承诺中访问它,但是返回的承诺将是你在ajax调用开始之前创建的承诺。

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


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

  1. 将文件读取封装到一个局部函数中,以避免在循环中声明函数,并允许所有核心逻辑流只使用承诺而不是承诺和回调的混合(例如封装回调)。
  2. 为文件读取器添加错误处理
  3. 切换延迟回调模型(std承诺使用)
  4. 删除了延迟的反模式,而只是返回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();
}
因此,现在我们可以使用$.myAjax函数与$.ajax相同的方法:
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 ...
});