如何使JavaScript代码按顺序执行?

25

好的,我理解JavaScript不同于C#或PHP,但是我一直在使用JavaScript遇到问题 - 不是JS本身的问题,而是我的使用方式。

这是我的一个函数:

function updateStatuses(){

showLoader() //show the 'loader.gif' in the UI

updateStatus('cron1'); //performs an ajax request to get the status of something
updateStatus('cron2');
updateStatus('cron3');
updateStatus('cronEmail');
updateStatus('cronHourly');
updateStatus('cronDaily');

hideLoader(); //hide the 'loader.gif' in the UI

}

问题在于,由于JavaScript热切地希望在代码中跳转,因此加载程序从未出现,因为“hideLoader”函数紧随其后运行。

我该如何解决这个问题?换句话说,我如何使JavaScript函数按照我在页面上编写的顺序执行...


Promise是处理所有异步请求的最佳对象。 使用Promise对象修复异步请求 - Iman Bahrampour
9个回答

16
问题发生的原因是AJAX的本质是异步的。这意味着updateStatus()调用确实按顺序执行,但会立即返回,并且JS解释器在从AJAX请求检索任何数据之前到达了hideLoader()
应该在AJAX调用完成的事件上执行hideLoader()

准确地说,在开始下一个任务之前,JS不会等待updateStatus任务完成。如果要让cron1在cron2开始之前完成,等等,您需要将每个updateStatus设置为在前一个调用成功时运行。 - MindStalker

14

如果您正在进行AJAX编程,需要将JavaScript视为基于事件而不是过程。在执行第二个调用之前,必须等待第一个调用完成。要实现这一点,需要将第二个调用绑定到当第一个调用完成时触发的回调函数。如果不了解您的AJAX库的内部工作方式(希望您正在使用库),我无法告诉您如何做到这一点,但它可能看起来像这样:

showLoader();

  updateStatus('cron1', function() {
    updateStatus('cron2', function() {
      updateStatus('cron3', function() {
        updateStatus('cronEmail', function() {
          updateStatus('cronHourly', function() {
            updateStatus('cronDaily', funciton() { hideLoader(); })
          })
        })
      })
    })
  })
});

这个想法是,updateStatus函数会接受它的普通参数,以及一个回调函数作为参数,当它完成时将执行此函数。把一个运行onComplete的函数传递给提供此钩子的函数是一种相当常见的模式。

更新

如果您使用jQuery,则可以在此处了解有关$.ajax()的更多信息:http://api.jquery.com/jQuery.ajax/

您的代码可能看起来像这样:

function updateStatus(arg) {
  // processing

  $.ajax({
     data : /* something */,
     url  : /* something */
  });

  // processing
}

你可以通过以下方式修改你的函数,使其接受回调函数作为第二个参数:

function updateStatus(arg, onComplete) {
  $.ajax({
    data : /* something */,
    url  : /* something */,
    complete : onComplete // called when AJAX transaction finishes
  });

}


10

我认为你所需要做的只是将以下代码添加到你的程序中:

async: false,

那么你的 Ajax 调用将如下所示:

jQuery.ajax({
            type: "GET",
            url: "something.html for example",
            dataType: "html",
            async: false,
            context: document.body,
            success: function(response){

                //do stuff here

            },
            error: function() {
                alert("Sorry, The requested property could not be found.");
            }  
        });

显然,对于XMLJSON等格式需要进行一些更改,但是async: false,是主要的点,它告诉JS引擎等待成功调用返回(或失败,取决于情况),然后继续执行。请注意,这样做有一个缺点,就是整个页面在ajax返回之前变得无响应!!!通常在毫秒级别内完成,这不是什么大问题,但可能需要更长时间。

希望这是正确的答案,并且对你有所帮助:)


这个解决了问题。简单明了的解决方案。非常感谢。 - darksoulsong
虽然这个方法可以工作,但不是一个很好的答案。你希望JS是异步的。有许多优点和场景需要让它自己去做事情,关闭整个应用程序的所有功能并不常见。我建议使用Promise库。这里有一个很棒的教程。http://dailyjs.com/2014/02/20/promises-in-detail/ - Codex

5
我们在项目中遇到类似的情况,通过使用计数器解决了这个问题。每次调用updateStatus时增加计数器,在AJAX请求的响应函数中减少计数器(具体取决于你使用的AJAX JavaScript库)。当计数器达到零时,所有的AJAX请求都已完成,此时你可以调用hideLoader()
以下是一个示例:
var loadCounter = 0;

function updateStatuses(){
    updateStatus('cron1'); //performs an ajax request to get the status of something
    updateStatus('cron2');
    updateStatus('cron3');    
    updateStatus('cronEmail');
    updateStatus('cronHourly');
    updateStatus('cronDaily');
}

function updateStatus(what) {
    loadCounter++;

    //perform your AJAX call and set the response method to updateStatusCompleted()
}

function updateStatusCompleted() {
    loadCounter--;
    if (loadCounter <= 0)
        hideLoader(); //hide the 'loader.gif' in the UI
}

嗨,Prutswonder,我刚试了一下,它可以工作!我喜欢这个解决方案。 - Ed.
3
如果你喜欢这些解决方案中的一个,请通过单击该答案旁边的复选框来标记你选择的最佳解决方案。 - Tim Snowhite
+1 对于这样一个深度嵌套的回调序列,这可能比我的解决方案更好。 - user229044
1
你可能想让 loadCounter < 0 的情况引发错误,因为如果它发生了,那就是个 bug。 - Matthew Crumley

2

这与代码的执行顺序无关。

加载图像从未显示的原因是在函数运行时UI不会更新。如果您在UI中进行更改,它们不会出现直到您退出函数并将控制权返回给浏览器。

您可以在设置图像后使用超时,让浏览器有机会在开始其余代码之前更新UI:

function updateStatuses(){

  showLoader() //show the 'loader.gif' in the UI

  // start a timeout that will start the rest of the code after the UI updates
  window.setTimeout(function(){
    updateStatus('cron1'); //performs an ajax request to get the status of something
    updateStatus('cron2');
    updateStatus('cron3');
    updateStatus('cronEmail');
    updateStatus('cronHourly');
    updateStatus('cronDaily');

    hideLoader(); //hide the 'loader.gif' in the UI
  },0);
}

还有另一个因素也可能导致您的代码看起来执行顺序出现问题。如果您的AJAX请求是异步的,函数不会等待响应。负责响应的函数将在浏览器接收到响应时运行。如果您想在收到响应后隐藏加载程序图像,则必须在最后一个响应处理程序函数运行时执行该操作。由于响应不必按照您发送请求的顺序到达,因此需要计算收到多少个响应才能知道何时到达最后一个响应。


多亏了Guffa和Dr. Sbaitso,我对底层发生的事情有了更多的了解,我打算尝试一些你们的建议,看看能实现什么效果!谢谢! - Ed.

1

安装Firebug,然后在showLoader、updateStatus和hideLoader的每一行中添加类似于这样的代码:

Console.log("event logged");

在控制窗口中,您将看到对您的函数的调用列表,并且它们将按顺序显示。问题是,“updateStatus”方法是做什么的?

可能它启动了一个后台任务,然后返回,这样在任何后台任务完成之前,您都会到达隐藏加载器的调用。您的Ajax库可能有“OnComplete”或“OnFinished”回调-从那里调用以下updateStatus。


updateStatus方法触发了一个jQuery ajax调用。我不能在“Onsuccess”回调中隐藏加载器的原因是我需要等待多个调用完成。但是使用下面给出的“counter”方法,我已经找到了一个解决方法。 - Ed.

1

正如其他人所指出的,您不想进行同步操作。拥抱异步,这就是 AJAX 中 A 的含义。

我想提及一个关于同步与异步的优秀比喻。您可以阅读 GWT 论坛上的 整篇文章,我只包含相关的比喻。

请想象一下……

您坐在沙发上看电视,知道自己没有啤酒了,于是请求您的配偶去酒店买些回来。当您看到您的配偶走出前门时,您从沙发上站起来,走进厨房并打开冰箱。令您惊讶的是,没有啤酒!

当然没有啤酒,因为您的配偶还在去酒店的路上。在他/她返回之前,您必须等待才能喝啤酒。

但是,您说您想要同步?再次想象一下……

当配偶离开房门时,你周围的整个世界都停止了, 你无法呼吸、开门或观看节目, 而 [他/她] 跑过镇子去拿你的啤酒。 你只能坐在那里一动不动,直到失去知觉变成青色...... 醒来后,发现自己被急救人员包围着,配偶说:“嘿,我拿到你的啤酒了。” 这正是你坚持进行同步服务器调用时会发生的事情。

0
处理所有异步请求的最佳解决方案之一是'Promise'
Promise对象表示异步操作的最终完成(或失败)。

示例:

let myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // In this example, we use setTimeout(...) to simulate async code. 
  // In reality, you will probably be using something like XHR or an HTML5 API.
  setTimeout(function(){
    resolve("Success!"); // Yay! Everything went well!
  }, 250);
});  

myFirstPromise.then((successMessage) => {
  // successMessage is whatever we passed in the resolve(...) function above.
  // It doesn't have to be a string, but if it is only a succeed message, it probably will be.
  console.log("Yay! " + successMessage);
});

Promise

如果您有3个异步函数,并希望按顺序运行,请按以下方式执行:

let FirstPromise = new Promise((resolve, reject) => {
    FirstPromise.resolve("First!");
});
let SecondPromise = new Promise((resolve, reject) => {

});
let ThirdPromise = new Promise((resolve, reject) => {

});
FirstPromise.then((successMessage) => {
  jQuery.ajax({
    type: "type",
    url: "url",
    success: function(response){
        console.log("First! ");
        SecondPromise.resolve("Second!");
    },
    error: function() {
        //handle your error
    }  
  });           
});
SecondPromise.then((successMessage) => {
  jQuery.ajax({
    type: "type",
    url: "url",
    success: function(response){
        console.log("Second! ");
        ThirdPromise.resolve("Third!");
    },
    error: function() {
       //handle your error
    }  
  });    
});
ThirdPromise.then((successMessage) => {
  jQuery.ajax({
    type: "type",
    url: "url",
    success: function(response){
        console.log("Third! ");
    },
    error: function() {
        //handle your error
    }  
  });  
});

通过这种方法,您可以按照自己的意愿处理所有异步操作。


0
将updateStatus调用移动到另一个函数中。使用新函数作为目标调用setTimeout。
如果您的ajax请求是异步的,您应该有一些方法来跟踪哪些请求已完成。每个回调方法可以为自己设置一个“completed”标志,并检查它是否是最后一个完成的标志。如果是,则调用hideLoader。

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