你如何让javascript代码执行*按顺序*

Posted

技术标签:

【中文标题】你如何让javascript代码执行*按顺序*【英文标题】:How do you make javascript code execute *in order* 【发布时间】:2011-02-07 22:05:00 【问题描述】:

好的,我很欣赏 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 是处理所有异步请求的最佳对象。 fix async requests with Promise object 【参考方案1】:

出现问题是因为 AJAX 本质上是异步的。这意味着 updateStatus() 调用确实按顺序执行,但立即返回,并且 JS 解释器在从 AJAX 请求中检索任何数据之前到达 hideLoader()

您应该在 AJAX 调用完成的事件上执行hideLoader()

【讨论】:

没错,JS 在开始下一个作业之前不会等待 updateStatus 作业完成。如果 cron1 必须在 cron2 启动之前完成,等等。您需要将每个 updateStatus 设置为在上一次调用成功时运行。【参考方案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 和 Sbaitso 博士,我明白了很多,知道背后发生了什么,我将尝试你的一些建议,看看我能做些什么! 【参考方案3】:

安装 Firebug,然后在 showLoader、updateStatus 和 hideLoader 中添加这样的一行:

Console.log("event logged");

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

大概它会启动一个后台任务,然后返回,因此您将在任何后台任务完成之前调用 hideLoader。您的 Ajax 库可能有一个“OnComplete”或“OnFinished”回调 - 从那里调用以下 updateStatus。

【讨论】:

updateStatus 方法触发 jquery ajax 调用。我无法在“Onsuccess”回调中隐藏加载程序的原因是我需要等待多个调用完成。但是使用下面给出的“计数器”方法,我有一个解决方法【参考方案4】:

如果您在进行 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
  );

【讨论】:

【参考方案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,如果您喜欢这些解决方案之一,您能否通过单击此人答案旁边的复选框来标记您选择的最佳解决方案? +1 对于如此深度嵌套的一系列回调,可能比我的解决方案更好 您可能希望loadCounter &lt; 0 的情况导致错误,因为如果发生这种情况,那就是错误。【参考方案6】:

将 updateStatus 调用移至另一个函数。以新函数为目标调用 setTimeout。

如果您的 ajax 请求是异步的,您应该有一些东西来跟踪哪些请求已完成。每个回调方法都可以在某处为自己设置一个“完成”标志,并检查它是否是最后一个这样做的。如果是,则让它调用 hideLoader。

【讨论】:

【参考方案7】:

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

我只想提一个关于同步与异步的很好的类比。你可以看entire post on the GWT forum,我只是包括相关的类比。

想象一下,如果你愿意......

你坐在沙发上看 电视,并且知道你已经离开了 啤酒,你请你的配偶来取悦 跑到酒类商店和 给你拿一些。一看到就 你的配偶走出前门, 你从沙发上爬起来 走进厨房,打开 冰箱。令你惊讶的是,没有 啤酒!

当然没有啤酒,你的 配偶仍在旅行中 酒品店。你得等到 [s]他在你预料到之前就回来了 喝杯啤酒。

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

...配偶走出门...现在, 你周围的整个世界都停止了,你 不要呼吸,回答 门,或看完你的节目 当[s]他跑过城镇去 拿你的啤酒。你只需要坐下 没有移动肌肉,并且 变蓝直到你输 意识……唤醒一些 无限期之后被包围 EMT 和配偶说哦,嘿,我 有你的啤酒。

这正是您坚持进行同步服务器调用时发生的情况。

【讨论】:

【参考方案8】:

我认为你需要做的就是在你的代码中包含这个:

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 返回!!!通常在几毫秒内,这不是什么大问题,但可能需要更长的时间。

希望这是正确的答案,它可以帮助你:)

【讨论】:

这个成功了。简单而简洁的解决方案。非常感谢。 虽然有效,但这并不是一个很棒的答案。你希望 JS 是异步的。有许多优势和场景,您希望它自己做,而为您的整个应用程序将其全部关闭是不常见的。我建议改用 Promise 库。这是一个很棒的教程。 dailyjs.com/2014/02/20/promises-in-detail【参考方案9】:

处理所有异步请求的最佳解决方案之一是'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
      
  );  
);

使用这种方法,您可以随心所欲地处理所有异步操作。

【讨论】:

以上是关于你如何让javascript代码执行*按顺序*的主要内容,如果未能解决你的问题,请参考以下文章

如何确保JavaScript的执行顺序

如何确保JavaScript的执行顺序

面试官:你说说如何让“线程”按序执行?

如何让 BackgroundWorker ProgressChanged 事件按顺序执行?

浏览器中的 JavaScript执行机制:07 | 变量提升:JavaScript代码是按顺序执行的吗?

如何让多个线程顺序执行