使用jQuery的load方法和Promises

22

我还在努力理解deferred等内容,因此我有一个问题,想知道如何实现以下功能。

我的团队和我有三个不同的.load()方法,每个方法都会抓取特定的模板并将其附加到同一容器中。每次加载的时间不同,因此当内容加载时,会以“阶梯式”的方式(1、2、3)加载。我想利用deferred对象,等待它们全部完成,然后同时将它们附加到容器中,以消除“阶梯式”操作。

$('<div>').load(baseInfoTemplate, function () {
    var baseData = {
        // build some object
    };

    $.tmpl(this, baseData).appendTo($generalContainer);
});

这三个调用与上面的调用类似。

我该如何实现这个?


它们被附加的顺序是否重要? - Alnitak
如果每个模板都有自己的容器,而不是将它们附加到同一个容器中,那么解决方案会更容易。 - Alnitak
顺序很重要...我同意如果它们有自己的话会很好 :) - Mike Fielden
你能这样做吗:<div id="c"> <div id="part1"/> <div id="part2"/> ... </div> - Alnitak
5个回答

9
我会为此案例使用以下代码:

我使用下面的代码:

$.when(
    $.get('templates/navbar.tmpl.html', function(data) {
        $('#navbar').html(data);
    }),
    $.get('templates/footer.tmpl.html', function(data) {
        $('#footer').html(data);
    }),
    $.Deferred(function(deferred) {
        $(deferred.resolve);
    })
).done(function() {
    $.getScript("js/tools/jquery.min.js");
});

在我的看法中,它比以前的实现更有结构并且非常漂亮。

是的,我喜欢能够看到我正在加载哪些模板,这是个好的解决方案。 - Paranoid Android

6
你可以将promise对象存储在数组中,并使用$.when()来判断这些promise是否已经被实现。代码示例如下:
var templates = [ ];

function createPromise( baseInfoTemplate ) {
    return $.Deferred(function( promise ) {
        $('<div>').load(baseInfoTemplate, function() {
            var baseData = { /* foobar */ };

            templates.push( baseData );
            promise.resolve();
        });
    }).promise();
}

var myPromises = [ ];

myPromises.push( createPromise( 'some data' ) );
myPromises.push( createPromise( 'even moar data' ) );
myPromises.push( createPromise( 'foo bar heay' ) );

$.when.apply( null, myPromises ).done( function() {
    templates.forEach(function( template ) {
        $.tmpl(this, template).appendTo($generalContainer);
    });
});

我在这里使用.apply(),因为它接受一个数组作为函数调用的参数。所以基本上,我们将所有promise对象传递给.when()
示例: http://jsfiddle.net/Hg77A/1/ 更新:
正如Alnitak指出的那样,如果没有"success"回调处理程序,上面的示例将没有多大意义。如果仅在使用.load()传输数据后触发"all done"处理程序,则只需要在.load()success handler.resolve() promises。这有任何意义吗?

这似乎无法正常工作 - 所有的片段都会逐一渲染 - Alnitak
@Alnitak: 真的。我对.tmpl()并不是很熟悉,但我希望它能提供某种“完成回调”,在那里你可以解决一个promise对象。如果不存在这样的回调,使用promises就毫无意义了。好观点。 - jAndy
仍然可以使用Deferred对象,但如果必须按正确顺序放置三个模板,则不会像可能那样容易。 - Alnitak
无论如何,$.load()确实有一个成功处理程序,但是您的代码在每个模板完成时呈现模板,而不是在它们全部完成时呈现。 - Alnitak
更好的方法是使用带有Promise的jQuery AJAX,查看 https://gist.github.com/Tusko/dfe1fcbbb71fc0a36c86cdd79530d1fc - Tusko Trush

5

$.load()并不适用于与Deferred对象一起使用,而且它的设计目的是立即将内容放入页面中。

要解决后面的问题,您必须在DOM之外渲染整个容器,然后在所有操作完成后再将其放入DOM中,或者您需要累加三个结果,然后一次性将它们全部放入其中。

下面的过程采用了后一种方法:

  1. 使用$.get()代替,并创建一个由$.get()返回的jqXHR对象数组

  2. 也将每个$.get()返回的HTML片段存储在一个数组中

  3. 使用$.when.apply($, myArray).done(function(){...})应用模板并将它们放入DOM中

请参见http://jsfiddle.net/alnitak/WW3ja/


使用.get()的唯一问题是我无法利用load()使用哈希加载页面片段... - Mike Fielden
@Mike 没错,选择选项 1,但将模板渲染到屏幕外的 DIV 中。然后在最终的 $.done() 回调中将 那个 DIV 添加到页面中。 - Alnitak
我喜欢这个想法,但是load()不会返回一个jqXHR对象,它只会返回jQuery。因此,.done()会立即触发而不是等待。我是否需要切换到.get()并将模板移动到它们自己的文件中来实现我的目标? - Mike Fielden
2
@Mike 不行 - 你可以为每个 $.load() 创建一个 $.Deferred(),然后将 def[n].resolve 设置为 $.load() 中的完成回调。对于每个模板仍然最好有一个单独的 div。 - Alnitak
@Alnitak 大部分是你的功劳 :) 我决定将模板移到它们自己的文件中,并使用 $.get(),一旦我这样做了,它就很好地解决了... 我感谢你的帮助! - Mike Fielden

2
这里有一个小型jQuery插件的代码片段(点击此处),它为一组jQuery元素添加了一个loadThen函数。它基本上是没有回调的load()函数,并返回一个promise,只有在内容加载并插入到所选元素集后才会被解决。
它基本上是jQuery自己的load()代码的复制/粘贴,除了它返回实际ajax调用的promise。这使您可以在ajax失败时获得拒绝的promise。
由于它基于load()功能,因此您可以在url后面添加一个选择器,以空格分隔,仅获取已加载html的一部分。
示例1:将此站点的主页加载到id =“container”的元素中。
$('#container').loadThen('/').then(function () {
    // loaded and ready.
}, function () {
    // error
});

示例二:将主页的标题加载到此页面的标题中。
$('h1').eq(0).loadThen('/ h1').then(function () {
    // loaded and ready.
}, function () {
    // error
});

要点内容:

(function ($) {
    var _loadThen = $.fn.loadThen;
    $.fn.loadThen = function (url, params) {
        if (typeof url !== "string" && _loadThen) {
            return _loadThen.apply(this, arguments);
        }

        if(this.length <= 0) {
            return jQuery.Deferred().resolveWith(this, ['']);
        }

        var selector, type, response,
            self = this,
            off = url.indexOf(" ");

        if (off >= 0) {
            selector = jQuery.trim(url.slice(off));
            url = url.slice(0, off);
        }

        if (params && typeof params === "object") {
            type = "POST";
        }

        return jQuery.ajax({
            url: url,
            type: type,
            dataType: "html",
            data: params
        }).then(function (responseText) {
                self.html(selector ? jQuery("<div>").append(jQuery.parseHTML(responseText)).find(selector) : responseText);
            return self;
        });
    };
}(jQuery));

2
我将以下代码用作通用库。
var loader = {};
(function(){
  var fn = {  
    promises: [],
    templates: [],

    loadTemplate: function( name ) {
      fn.promises.push(
        $.get( `templates/${name}.tmpl.html`,
               (html) => fn.templates.push( html )
        )
      );
    },

    main: function( templates, callback ) {
      templates.forEach( (template) => fn.loadTemplate( template ));
      $.when.apply( $, fn.promises ).done( function() {
        $( '<div id="templates">' ).html( fn.templates.join() ).appendTo( 'body' );
        callback();
      });
    }
  };

  /* EXPORTS */
  loader.main = fn.main;
})();

然后在应用程序的主JS文件中将其作为第一个函数调用。
function myMain() {
  ... // do all the things
}

$( document ).ready( function() {
  templates = [ 'thingies', 'wotsits', 'oojamaflips' ];
  loader.main( templates, () => myMain());
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接