Отложенные обещания jQuery выполняются не по порядку?

РЕДАКТИРОВАТЬ: ВАЖНОЕ ПРИМЕЧАНИЕ. Здесь используется jQuery 1.7.2, и нет, его нельзя изменить с этой версии

Я новичок в обещаниях и пытаюсь обдумать их. Я пытаюсь выполнить ряд функций по порядку, ожидая их завершения, прежде чем создавать дочерние представления (это в 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'

Что не так с моими обещаниями и почему они не выполняются в ожидаемом порядке?


person tdc    schedule 25.09.2015    source источник
comment
Я не знаком с промисами, но вы используете здесь вызов ajax, и это асинхронно! Может быть причиной того, что ваши звонки не по порядку. Вы можете попробовать добавить успешную функцию в ajax, и после успеха выполните следующий шаг. Я вижу там другие вызовы функций. Это тоже асинхронные? Возможно, вы захотите изучить функции обратного вызова, если они существуют.   -  person Kamy D    schedule 26.09.2015
comment
Обещания @bravekido также асинхронны. $.ajax возвращает обещание. OP полностью осведомлен об асинхронной проблеме и пытается организовать синхронизацию порядка обратных вызовов.   -  person charlietfl    schedule 26.09.2015
comment
@bravekido да, это вызовы ajax, но мне нужно дождаться их завершения (или сбоя), прежде чем выполнять оставшуюся часть страницы, как объяснил Чарлифтл.   -  person tdc    schedule 26.09.2015
comment
Со старым jQuery вы захотите использовать pipe вместо then   -  person Bergi    schedule 13.11.2015


Ответы (1)


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

кажется, вызывает loadScripts() , createChildViews() немедленно, вместо этого попробуйте ссылаться на имена функций в .then(_this.loadAdScripts) callbacks .

return def.resolve() возвращает объект jQuery.Deferred(), а не объект обещания jQuery; попробуйте добавить .promise() после .resolve(), чтобы вернуть объект обещания 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 без изменения исходного кода.

Возвращает ожидаемый порядок при использовании jQuery версии 1.8+ после обновления deferred.then ; см. 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>

person guest271314    schedule 25.09.2015
comment
Я не минусовал, но... описание ответа? Что изменилось? Просто ОП вызывал функции и передавал результаты .then()? Не заставляйте нас сравнивать ваш код построчно с оригиналом. - person nnnnnn; 26.09.2015
comment
Я тоже не минусовал, но описание было бы неплохо :) - person tdc; 26.09.2015
comment
А код только find the difference ответ без пояснений не очень качественный - person charlietfl; 26.09.2015
comment
Попытка включить описание небольших корректировок в комментарии, обновит сообщение. - person guest271314; 26.09.2015
comment
вы все еще можете редактировать, но текущее решение не работает - person tdc; 26.09.2015
comment
@Prefix Возвращает ожидаемый порядок при использовании jQuery версии 1.8+ после обновления .then() api.jquery.com/ deferred.then jsfiddle jsfiddle.net/1tonoL6x - person guest271314; 26.09.2015
comment
@ guest271314 интересно - к сожалению, из-за некоторых требований проекта я не могу изменить версию jQuery :( Разве это невозможно с 1.7.2? - person tdc; 26.09.2015
comment
кроме того, даже в вашем примере 1.7.2 функция дочерних представлений (4) все еще срабатывает до загрузки скрипта (2) (3). Мне нужно убедиться, что скрипты загружены и проанализированы перед созданием дочерних представлений. - person tdc; 26.09.2015
comment
@Prefix Смотрите обновленный пост. Перемещено .then(_this.createChildViews) во вторую цепочку .then() после .insertScripts() вызова ; кажутся возвращающими ожидаемый порядок. - person guest271314; 27.09.2015