简体   繁体   中英

How to correctly use Promise.then() in JavaScript?

I'm trying to run some functions one after another using Promises but somehow the second function is either executed before the first one or not at all. The situation is this:

I have the following have functions:

  • One that loads re-usable html code from a partials folder:
    function insertingPartials() {
        return new Promise( function(resolve,reject) {
            $('#navbar-placeholder').load('/Assets/Partials/navbar.html');
            $('#jumbotron-placeholder').load('/Assets/Partials/jumbotron.html');
            $('#footer-placeholder').load('/Assets/Partials/footer.html');

            resolve();
            reject('Error');
        });
  • One that makes language adjustments
function languageSpecifics() {
    return new Promise( function(resolve,reject) {
        //showing correct text per html language
        $('span[lang=' + $('html')[0].lang + ']').show();
        $('div[lang=' + $('html')[0].lang + ']').show();
        //disabling the current language from the language selection menu
        $('a[lang=' + $('html')[0].lang + ']').addClass('disabled');
        //links dynamically point to the correct sub-pages
        $('.blog-link').attr('href', '/' + $('html')[0].lang + '/Blog/');
        $('.prod-link').attr('href', '/' + $('html')[0].lang + '/' + $('.prod-link span[lang=' + $('html')[0].lang + ']').text() + '/');
        $('#en').attr('href', window.location.href.replace($('html')[0].lang, 'en'));
        $('#es').attr('href', window.location.href.replace($('html')[0].lang, 'es'));
        $('#ro').attr('href', window.location.href.replace($('html')[0].lang, 'ro'));

        resolve();
        reject('Error in ' + arguments.callee.name);
    });
}
  • One that slides content into view:
function loadContent() {
    return new Promise( function(resolve,reject) {
        //fading content in
        $('nav').animate({top: '0'});
        $('footer').animate({bottom: '0'});
        $('.main-content').animate({right: '0'}).css('overflow', 'auto');
        //fading preloading out
        $('.spinner-border').fadeOut();
        $('#preloading').removeClass('d-flex').addClass('d-none');

        resolve();
        reject('Error in ' + arguments.callee.name);
    });
}
  • and one that adjusts the height of the container
function setContainerHeight() {
    //setting the height of the container
    $('.container').css('height', $('body').height() - ($('nav').height() + $('footer').height()) + 'px');
}

What I'm trying to do is have the functions execute in the order in which I placed them above. The below code outputs 1,2,3,4 yet function "languageSpecifics" doesn't execute or it executes before "insertingPartials", because the partials are loaded and the components then slide into view, yet text cannot be seen and neither do the links point to anywhere.

$(document).ready( function() {

    console.log('1')
    insertingPartials().then( function() {
        console.log('2');
        languageSpecifics().then( function() {
            console.log('3');
            loadContent().then( function() {
                console.log('4');
                setContainerHeight();
            });
        });
    });

});

If I execute the functions separately in the browser console I get the desired output and every promise returns fulfilled. If I run them with .then() the promise returns pending and I don't get any text on the page. ( both nested ".then(.then() ) and same level ".then().then()" give the same results)

I'd like to know what I'm doing wrong here. Also if there is a better / more efficient way of achieving what I'm trying to do here please advise.

Can't comment yet. Afaik .load() is an asynchronous function, which means the following resolve is called before the pages are loaded. You should try using .load()'s callback parameter and call resolve only after all of them are finished.

If you want synchronous behavior (ie fire one function after another in order), try async function and await keyword.

  • Wrap each of your functions in a Promise include a Number for time in ms or s:

     const _A_ = () => { return new Promise(resolve => { setTimeout(() => resolve({{FUNCTION}}), {{TIME}}); }); } 
  • Wrap an async function around all of the Promises:

     const _Y_ = async() => { ... /* Promises */ } 
  • At the end of the async function call each Promise in order with the keyword await :

     const _Y_ = async() => { ... /* Promises */ await _A_(); await _B_(); await _C_(); await _D_(); } 

The following demo does not work, if you want to review a functioning demo, go to this Plunker

 const main = document.forms[0]; const loader = async(time = 700) => { const ajax1 = () => { return new Promise(resolve => { setTimeout(() => resolve($('.base ol').append($('<li>').load('comp.html #set1'))), time); }); } const ajax2 = () => { return new Promise(resolve => { setTimeout(() => resolve($('.base ol').append($('<li>').load('comp.html #set2'))), time); }); } const ajax3 = () => { return new Promise(resolve => { setTimeout(() => resolve($('.base ol').append($('<li>').load('comp.html #set3'))), time); }); } const ajax4 = () => { return new Promise(resolve => { setTimeout(() => resolve($('.base ol').append($('<li>').load('comp.html #set4'))), time); }); } await ajax1(); await ajax2(); await ajax3(); await ajax4(); } const getComp = e => { e.preventDefault(); loader(); } main.onsubmit = getComp; 
 .set::before { content: attr(id); font: 400 16px/1 Consolas; } button { font: inherit; float: right; } 
 <!doctype html> <html> <head> </head> <body> <form id='main'> <fieldset class='base'> <legend>Synchronous AJAX</legend> <ol></ol> </fieldset> <button>GO</button> </form> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> </body> </html> 

load() is asynchronous and allows for a complete callback but not a promise

You could replace load() with $.get() which does return promise and use $.when() to be called after all 3 have loaded:

There is nothing asynchronous other than the ajax that would require using promises in your dom manipulation functions and you can just call those functions in the preferred order without any promises needed....only the initial loading promise

Something like:

// page load call
$(function(){
   loadAllPartials().then(function(){
      languageSpecifics();
      contentDisplay();   
   });
});

// get single partial and insert in dom, return promise
function loadPartial(url, selector) {
  return $.get(url).then(function(data) {
    $(selector).html(data);
  })
}

// return `$.when()` promise for loading all partials
function loadAllPartials() {
  return $.when(
    loadPartial('/Assets/Partials/navbar.html', '#navbar-placeholder'),
    loadPartial('/Assets/Partials/jumbotron.html', '#jumbotron-placeholder'),
    loadPartial('/Assets/Partials/footer.html', '#footer-placeholder')
  )
}

// adjusted to synchronous code, no promises needed
function contentDisplay() {

  //fading content in
  $('nav').animate({top: '0'});
  $('footer').animate({bottom: '0'});
  $('.main-content').animate({right: '0'}).css('overflow', 'auto');
  //fading preloading out
  $('.spinner-border').fadeOut();
  $('#preloading').removeClass('d-flex').addClass('d-none');

}

function languageSpecifics() {
  // store lang value once instead of searching dom each time
  var lang = $('html')[0].lang
  //showing correct text per html language
  $('span[lang=' + lang + ']').show();
  $('div[lang=' + lang + ']').show();
  //disabling the current language from the language selection menu
  $('a[lang=' + lang + ']').addClass('disabled');
  //links dynamically point to the correct sub-pages
  $('.blog-link').attr('href', '/' + lang + '/Blog/');
  $('.prod-link').attr('href', '/' + lang + '/' + $('.prod-link span[lang=' + lang + ']').text() + '/');
  $('#en').attr('href', window.location.href.replace(lang, 'en'));
  $('#es').attr('href', window.location.href.replace(lang, 'es'));
  $('#ro').attr('href', window.location.href.replace(lang, 'ro'));


}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM