【问题标题】:jQuery deferred promises executing out of order?jQuery延迟承诺无序执行?
【发布时间】:2015-12-23 19:14:45
【问题描述】:

编辑:重要说明这是使用 jQuery 1.7.2,不,不能从此版本更改

我对 promises 不熟悉,并试图围绕它们进行思考。我正在尝试按顺序执行一系列函数,等待它们完成,然后再创建一些子视图(这是在 Backbone.js 中)。这是我的代码:

initialize: function () {
    console.log('AppView::initialized!');
    var _this = this;

    $.when( _this.processCookies() )
        .then( _this.loadAdScripts() )
        .then( _this.createChildViews() );
},

processCookies: function () {
    var def = $.Deferred();
    console.log('(1) PROCESS COOKIES');
    return def.resolve();
},


/**
 * Instantiates new instances of the child views.
 */
createChildViews: function () {
    var _this = this;
    console.log('(4) CREATING CHILD VIEWS');
},

loadAdScripts: function () {

    var _this = this,
        def = $.Deferred();

    $.when(
        _this.insertScript({
            name: 'example1',
            async: false,
            src: '//www.example.com/script1.js',
        }),
        _this.insertScript({
            is_mobile: is_mobile,
            name: 'example2',
            async: true,
            src: '//example.com/script2.js'
        })
    )
    .done(function () {
        console.log('(3) ALL SCRIPTS LOADED');
        def.resolve();
    });
},

insertScript: function (script) {
    var def = $.Deferred(),
        protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');

    // dont script 2 on mobile.
    if (script.name === 'example2' && script.is_mobile) {
        console.log('skipping script');
        return def.resolve();
    }

    var promise = $.ajax({
        dataType: 'script',
        cache: false,
        async: script.async,
        url: protocol + script.src,
    });

    promise.done( function () {
        console.log('(2) SINGLE SCRIPT LOADED');
        return def.resolve();
    });


},

所以,这里所需的流程是:

  1. processCookies()函数完成后,
  2. 执行loadAdScripts函数 2a。 insertScript() 触发,脚本 1 加载 2b。 insertScript() 触发,脚本 2 加载
  3. 当两个脚本都完成后,执行createChildViews函数。

所以,观察代码中的console.log() 占位符,我期望在我的控制台中看到:

'(1) PROCESS COOKIES'
'(2) SINGLE SCRIPT LOADED'
'(2) SINGLE SCRIPT LOADED'
'(3) ALL SCRIPTS LOADED'
'(4) CREATING CHILD VIEWS'

然而实际上看到的是:

'(1) PROCESS COOKIES'
'(3) ALL SCRIPTS LAODED'
'(4) CREATING CHILD VIEWS'
'(2) SINGLE SCRIPT LOADED'
'(2) SINGLE SCRIPT LOADED'

我的承诺有什么问题,为什么它们没有按预期的顺序执行?

【问题讨论】:

  • 我对 Promise 不熟悉,但您在这里使用的是 ajax 调用,这是异步的!可能是您的电话出现故障的原因。您可以尝试将成功的功能添加到ajax,一旦成功,执行下一步。我在那里看到其他函数调用。那些也是异步的吗?如果存在回调函数,您可能需要查看回调函数。
  • @bravekido 承诺也是异步的。 $.ajax 返回一个承诺。 OP 完全意识到异步问题,并试图组织回调顺序的时间
  • @bravekido 是的,它们是 ajax 调用,但我需要等待它们完成(或失败),然后再执行页面的其余部分,就像 charlieftl 解释的那样。
  • 对于旧的 jQuery,您需要使用 pipe 而不是 then

标签: javascript jquery backbone.js jquery-deferred


【解决方案1】:
$.when( _this.processCookies() )
        .then( _this.loadAdScripts() )
        .then( _this.createChildViews() );

似乎是立即调用loadScripts()createChildViews(),而是尝试在.then(_this.loadAdScripts)回调中引用函数名称。

return def.resolve() 返回 jQuery.Deferred() 对象,而不是 jQuery 承诺对象;尝试在.resolve() 之后添加.promise() 以返回jQuery 承诺对象。

jQuery.ajax() 返回 jQuery 承诺对象;不需要创建新的 jQuery $.Deferred() 对象,可以返回 $.ajax()

initialize: function () {
    console.log('AppView::initialized!');
    var _this = this;

    $.when( _this.processCookies() )
        // reference function name, not invoked
        .then( _this.loadAdScripts )
        .then( _this.createChildViews );
},

processCookies: function () {
    // no asynchronous operations appear here, 
    // no need to include `$.Deferred()` or `.promise()` object
    // var def = $.Deferred();
    console.log('(1) PROCESS COOKIES');
    // return jQuery promise object, not deferred object
    // return def.resolve().promise();
},


/**
 * Instantiates new instances of the child views.
 */
createChildViews: function () {
    var _this = this;
    console.log('(4) CREATING CHILD VIEWS');
},

loadAdScripts: function () {

    //var _this = this,
    //    def = $.Deferred();

    return $.when(
        _this.insertScript({
            name: 'example1',
            async: false,
            src: '//www.example.com/script1.js',
        }),
        _this.insertScript({
            is_mobile: is_mobile,
            name: 'example2',
            async: true,
            src: '//example.com/script2.js'
        })
    )
    .done(function () {
        console.log('(3) ALL SCRIPTS LOADED');
        // def.resolve();
    });
},

insertScript: function (script) {
    // var def = $.Deferred(),
        protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');

    // dont script 2 on mobile.
    // if (script.name === 'example2' && script.is_mobile) {
    //    console.log('skipping script');
    //    return def.resolve().promise();
    // }

    var promise = script.name === 'example2' && script.is_mobile 
                  ? $.when() 
                  : $.ajax({
                      dataType: 'script',
                      cache: false,
                      async: script.async,
                      url: protocol + script.src,
                    });

    promise.done( function () {
        console.log('(2) SINGLE SCRIPT LOADED');
        // def.resolve();
    });    
},

编辑:重要说明这是使用 jQuery 1.7.2,不,它不能 从这个版本改变

编辑、更新

如果不修改源代码,使用 jQuery 版本 1.7.2 可能无法实现预期的顺序。

deferred.then 更新后,使用 jQuery 版本 1.8+ 时出现返回预期顺序;见http://blog.jquery.com/2012/08/09/jquery-1-8-released/http://bugs.jquery.com/ticket/11010

1.8.0

var dfd = {
    initialize: function () {
        console.log('AppView::initialized!');
        _this = dfd;

        $.when(_this.processCookies())
        // reference function name, not invoked
        .then(_this.loadAdScripts)
        .then(_this.createChildViews);
    },

    processCookies: function () {
        // no asynchronous operations appear here, 
        // no need to include `$.Deferred()` or `.promise()` object
        var def = $.Deferred(function (d) {
            setTimeout(function () {
                d.resolve('(1) PROCESS COOKIES')
            }, Math.floor(Math.random() * 1000));
        }).promise();
        def.then(function (msg) {
            console.log(msg);
        });
        return def.promise()
    },


    /**
     * Instantiates new instances of the child views.
     */
    createChildViews: function () {
        _this = dfd;
        console.log('(4) CREATING CHILD VIEWS');
    },

    loadAdScripts: function () {
        _this = dfd;
        return $.when.apply(_this, [_this.insertScript({
            name: 'example1',
            async: false,
            src: '//www.example.com/script1.js',
        }),
        _this.insertScript({
            is_mobile: true,
            name: 'example2',
            async: true,
            src: '//example.com/script2.js'
        })]).then(function () {
            console.log('(3) ALL SCRIPTS LOADED');
        })
    },

    insertScript: function (script) {
        // var def = $.Deferred(),
        protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');

        // dont script 2 on mobile.
        // if (script.name === 'example2' && script.is_mobile) {
        //    console.log('skipping script');
        //    return def.resolve();
        // }

        var promise = $.when( script.name === 'example2' && script.is_mobile ? $.Deferred(function (d) {
            setTimeout(function () {
                d.resolve('(2) skipping script', protocol + script.src)
            }, Math.floor(Math.random() * 1000))
        }).promise() : $.Deferred(function (d) {
            setTimeout(function () {
                d.resolve('(2) SINGLE SCRIPT LOADED', protocol + script.src)
            }, Math.floor(Math.random() * 1000))
        }).promise())
        
        return promise.then(function(msg) {
          console.log(msg)
        });
    }
};

dfd.initialize();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>

1.72

var dfd = {
    initialize: function () {
        console.log('AppView::initialized!');
        _this = dfd;

        $.when(_this.processCookies())
        // reference function name, not invoked
        .then(_this.loadAdScripts)
        .then(_this.createChildViews);
    },

    processCookies: function () {
        // no asynchronous operations appear here, 
        // no need to include `$.Deferred()` or `.promise()` object
        var def = $.Deferred(function (d) {
            setTimeout(function () {
                d.resolve('(1) PROCESS COOKIES')
            }, Math.floor(Math.random() * 1000));
        }).promise();
        def.then(function (msg) {
            console.log(msg);
        });
        return def.promise()
    },


    /**
     * Instantiates new instances of the child views.
     */
    createChildViews: function () {
        _this = dfd;
        console.log('(4) CREATING CHILD VIEWS');
    },

    loadAdScripts: function () {
        _this = dfd;
        return $.when.apply(_this, [_this.insertScript({
            name: 'example1',
            async: false,
            src: '//www.example.com/script1.js',
        }),
        _this.insertScript({
            is_mobile: true,
            name: 'example2',
            async: true,
            src: '//example.com/script2.js'
        })]).then(function () {
            console.log('(3) ALL SCRIPTS LOADED');
        })
    },

    insertScript: function (script) {
        // var def = $.Deferred(),
        protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');

        // dont script 2 on mobile.
        // if (script.name === 'example2' && script.is_mobile) {
        //    console.log('skipping script');
        //    return def.resolve();
        // }

        var promise = $.when( script.name === 'example2' && script.is_mobile ? $.Deferred(function (d) {
            setTimeout(function () {
                d.resolve('(2) skipping script', protocol + script.src)
            }, Math.floor(Math.random() * 1000))
        }).promise() : $.Deferred(function (d) {
            setTimeout(function () {
                d.resolve('(2) SINGLE SCRIPT LOADED', protocol + script.src)
            }, Math.floor(Math.random() * 1000))
        }).promise())
        
        return promise.then(function(msg) {
          console.log(msg)
        });
    }
};

dfd.initialize();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>

【讨论】:

  • 我没有投反对票,但是...回答说明?有什么不同?只是 OP 正在调用函数并将结果传递给.then()?不要让我们逐行将您的代码与原始代码进行比较。
  • 我也没有投反对票,但描述会很好:)
  • 只有代码find the difference没有解释的回答质量不是很高
  • 已尝试包含对 cme​​ts 轻微调整的描述,将在帖子中更新
  • 您可能仍在编辑,但当前的解决方案不起作用
猜你喜欢
  • 2015-03-27
  • 2023-03-21
  • 1970-01-01
  • 2015-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多