【问题标题】:How to force Sequential Javascript Execution?如何强制顺序 Javascript 执行?
【发布时间】:2010-12-23 23:17:31
【问题描述】:

我只找到了涉及类、事件处理程序和回调的相当复杂的答案(在我看来,这似乎是一种大锤方法)。我认为回调可能有用,但我似乎无法在最简单的情况下应用这些。看这个例子:

<html>
  <head>
    <script type="text/javascript">
      function myfunction()  {
        longfunctionfirst();
        shortfunctionsecond();
      }

      function longfunctionfirst() {
        setTimeout('alert("first function finished");',3000);
      }

      function shortfunctionsecond() {
        setTimeout('alert("second function finished");',200);
      }
    </script>
  </head>
  <body>
    <a href="#" onclick="javascript:myfunction();return false;">Call my function</a>
  </body>
</html>

这里,第二个函数在第一个函数之前完成;什么是最简单的方法(或者有什么方法?)强制第二个函数延迟执行直到第一个函数完成?

---编辑---

所以这是一个垃圾示例,但感谢 David Hedlund,我在这个新示例中看到它确实是同步的(以及在测试过程中使我的浏览器崩溃!):

<html>
<head>

<script type="text/javascript">
function myfunction() {
    longfunctionfirst();
    shortfunctionsecond();
}

function longfunctionfirst() {
    var j = 10000;
    for (var i=0; i<j; i++) {
        document.body.innerHTML += i;
    }
    alert("first function finished");
}

function shortfunctionsecond() {
    var j = 10;
    for (var i=0; i<j; i++) {
        document.body.innerHTML += i;
    }
    alert("second function finished");
}
</script>

</head>

<body>
  <a href="#" onclick="javascript:myfunction();return false;">Call my function</a>
</body>
</html>

由于我的实际问题是 jQuery 和 IE,如果我自己无法解决问题,我将不得不发布一个单独的问题!

【问题讨论】:

  • 希望这会有所帮助github.com/dineshkani24/queuecall
  • 我很高兴不是我在为此苦苦挣扎。我开始使用 nodejs 来处理日志文件。关于该处理的所有内容都决定了顺序操作,但我花了很多时间试图强制执行所需的序列。我发现承诺和所有“然后这个”“那么那个”的链接我们最终得到了不可读的代码。

标签: javascript asynchronous callback execution synchronous


【解决方案1】:

好吧,setTimeout,根据其定义,不会阻止线程。这是可取的,因为如果这样做,它会在等待期间冻结整个 UI。如果你真的需要使用setTimeout,那么你应该使用回调函数:

function myfunction() {
    longfunctionfirst(shortfunctionsecond);
}

function longfunctionfirst(callback) {
    setTimeout(function() {
        alert('first function finished');
        if(typeof callback == 'function')
            callback();
    }, 3000);
};

function shortfunctionsecond() {
    setTimeout('alert("second function finished");', 200);
};

如果您使用setTimeout,但只是具有执行时间很长的函数,并且正在使用setTimeout 来模拟它,那么您的函数 实际上是同步的,你根本不会有这个问题。不过应该注意的是,AJAX 请求是异步的,并且就像setTimeout 一样,在 UI 线程完成之前不会暂停它。对于 AJAX,与 setTimeout 一样,您必须使用回调。

【讨论】:

  • 另外,我应该注意,AJAX 请求通常是异步的,但可以设为同步。
  • @Justin:是的,这是一个有效的观点,谢谢你的评论。如果问题的真实案例场景确实与 ajax 相关,那么同步回调可能是解决方案
  • (虽然 async-with-callback 方法可能仍然提供更好的执行流程控制和可读性)
  • 他没有在问题中要求 AJAX
  • @SukanyaPai:不,这个答案是 10 年前写的,用于解释传递回调的特定模式。在您的代码中,shortfunctionsecond 将在您调用它时首先被调用,而不仅仅是将其作为参数传递。 shortfunctionsecond(thirdFunction) 的结果将传递给 longfunctionfirst。处理您引入的复杂性的最新答案是使用Promises
【解决方案2】:

经过这么长时间,我又回到了这个问题,因为我花了很长时间才找到我认为是干净的解决方案: 我所知道的强制 javascript 顺序执行的唯一方法是使用 Promise。 在 Promises/APromises/A+ 有详尽的承诺解释

我知道的唯一实现 promise 的库是 jquery,所以这里是我如何使用 jquery promises 解决问题的方法:

<html>
<head>
    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
    <script type="text/javascript">
    function myfunction()
    {
        promise = longfunctionfirst().then(shortfunctionsecond);
    }
    function longfunctionfirst()
    {
        d = new $.Deferred();
        setTimeout('alert("first function finished");d.resolve()',3000);
        return d.promise()
    }
    function shortfunctionsecond()
    {
        d = new $.Deferred();
        setTimeout('alert("second function finished");d.resolve()',200);
        return d.promise()
    }
    </script>
</head>
<body>
    <a href="#" onclick="javascript:myfunction();return false;">Call my function</a>
</body>
</html>

通过实现一个承诺并使用 .then() 链接函数,您可以确保第二个函数只有在第一个函数执行后才会执行 longfunctionfirst() 中的命令 d.resolve() 给出了启动下一个函数的信号。

从技术上讲,shortfunctionsecond() 不需要创建一个 deferred 并返回一个 Promise,但是我爱上了 Promise,并且倾向于用 Promise 来实现所有东西,对不起。

【讨论】:

  • Promise 实际上是当前 (2015) ECMAscript 草案的一部分:people.mozilla.org/~jorendorff/…
  • 人们还在阅读这个旧答案吗?谢谢,是的,Javascript 继续前进,只是等待 2015 年草案赶上所有浏览器:-)
  • 大家好,我总是很惊讶这个旧胎面仍然引起人们的注意。是的,你是对的,现在 ES6 和越来越多的框架实现了 Promise。这是老东西,但 5 年前它把我逼疯了,是我在计算机领域学到的最难的一课。简而言之,我陷入了对计算的 Von Newmann 顺序理解,无法领会 Javascript 所拥护的事件驱动开发的美妙之处。
【解决方案3】:

我是编程方面的老手,最近又恢复了以前的热情,并且正在努力适应这个面向对象、事件驱动的光明新世界,虽然我看到了 Javascript 的非顺序行为的优势,但有时间它确实妨碍了简单性和可重用性。 我研究过的一个简单示例是拍摄一张照片(用 javascript、HTML、phonegap 等编程的手机),调整其大小并将其上传到网站上。 理想的顺序是:

  1. 拍照
  2. 将照片加载到 img 元素中
  3. 调整图片大小(使用 Pixastic)
  4. 上传到网站
  5. 成功失败时通知用户

如果我们让每个步骤在完成后将控制权返回到下一个步骤,这将是一个非常简单的顺序程序,但实际上:

  1. 拍照是异步的,所以程序会在 img 元素存在之前尝试将其加载到它中
  2. 加载照片是异步的,因此在 img 完全加载之前开始调整图片大小
  3. 调整大小是异步的,因此在图片完全调整大小之前开始上传到网站
  4. 上传到网站是异步的,所以程序会在照片完全上传之前继续。

顺便说一句,这 5 个步骤中有 4 个涉及回调函数。

因此,我的解决方案是将每个步骤嵌套在前一个步骤中并使用 .onload 和其他类似的策略,它看起来像这样:

takeAPhoto(takeaphotocallback(photo) {
  photo.onload = function () {
    resizePhoto(photo, resizePhotoCallback(photo) {
      uploadPhoto(photo, uploadPhotoCallback(status) {
        informUserOnOutcome();
      });
    }); 
  };
  loadPhoto(photo);
});

(我希望我没有犯太多错误,将代码带到它的本质是真实的东西太分散注意力)

这是我认为异步不好而同步好的完美示例,因为与 Ui 事件处理相反,我们必须在执行下一步之前完成每一步,但代码是俄罗斯娃娃结构,令人困惑并且不可读,由于所有的嵌套,很难实现代码的可重用性所有这些代码的结果会给我一个返回码,但第一个容器将在返回码可用之前完成。

现在回到 Tom 最初的问题,对于 15 年前使用 C 语言和愚蠢的电子板的非常简单的程序,智能、易于阅读、易于重用的解决方案是什么?

要求实际上非常简单,以至于我觉得我一定缺少对 Javsascript 和现代编程的基本理解,技术肯定是为了提高生产力,对吧?

感谢您的耐心等待

恐龙雷蒙德 ;-)

【讨论】:

  • 我在使用 phonegap 时遇到了同样的困难。我有一个充满嵌套回调函数的代码,很难阅读。在某些时候,我希望我已经在本地实现了该应用程序。我正在研究录音机并将记录的数据与服务器同步。我尝试使用 requirejs 和主干来简化我的代码。这有助于将事情分开并组织模块中的代码,但我仍然需要嵌套回调......
【解决方案4】:

在 javascript 中,没有办法让代码等待。我遇到了这个问题,我这样做的方式是对服务器进行同步 SJAX 调用,并且服务器实际上在返回之前执行睡眠或执行一些活动,并且整个时间,js 都在等待。

例如同步 AJAX:http://www.hunlock.com/blogs/Snippets:_Synchronous_AJAX

【讨论】:

  • SJAX 通常是糟糕设计的标志,我鼓励新手在了解其含义之前避免使用它。
【解决方案5】:

在您的示例中,第一个函数实际上在第二个函数启动之前完成。 setTimeout 在达到超时之前不会保持函数的执行,它只会在后台启动一个计时器并在指定时间后执行您的警报语句。

在 JavaScript 中没有原生的“睡眠”方式。您可以编写一个循环来检查时间,但这会给客户端带来很大压力。您也可以按照 emacsian 的描述进行同步 AJAX 调用,但这会给您的服务器带来额外的负载。你最好的办法是避免这种情况,一旦你了解了 setTimeout 的工作原理,这对大多数情况来说应该足够简单。

【讨论】:

    【解决方案6】:

    我尝试了回调方式,但无法让它工作,您必须了解的是,即使执行不是,值仍然是原子的。例如:

    alert('1');

    alert('2');

    但是这样做会迫使我们知道执行顺序:

    loop=2;
    total=0;
    for(i=0;i<loop;i++) {
               total+=1;
               if(total == loop)
                          alert('2');
               else
                          alert('1');
    }
    

    【讨论】:

      【解决方案7】:

      我遇到了同样的问题,这是我的解决方案:

      var functionsToCall = new Array();
      
      function f1() {
          $.ajax({
              type:"POST",
              url: "/some/url",
              success: function(data) {
                  doSomethingWith(data);
                  //When done, call the next function..
                  callAFunction("parameter");
              }
          });
      }
      
      function f2() {
          /*...*/
          callAFunction("parameter2");
      }
      function f3() {
          /*...*/
          callAFunction("parameter3");
      }
      function f4() {
          /*...*/
          callAFunction("parameter4");
      }
      function f5() {
          /*...*/
          callAFunction("parameter5");
      }
      function f6() {
          /*...*/
          callAFunction("parameter6");
      }
      function f7() {
          /*...*/
          callAFunction("parameter7");
      }
      function f8() {
          /*...*/
          callAFunction("parameter8");
      }
      function f9() {
          /*...*/
          callAFunction("parameter9");
      }
          
      function callAllFunctionsSy(params) {
      	functionsToCall.push(f1);
      	functionsToCall.push(f2);
      	functionsToCall.push(f3);
      	functionsToCall.push(f4);
      	functionsToCall.push(f5);
      	functionsToCall.push(f6);
      	functionsToCall.push(f7);
      	functionsToCall.push(f8);
      	functionsToCall.push(f9);
      	functionsToCall.reverse();
      	callAFunction(params);
      }
      
      function callAFunction(params) {
      	if (functionsToCall.length > 0) {
      		var f=functionsToCall.pop();
      		f(params);
      	}
      }

      【讨论】:

        【解决方案8】:

        如果你不坚持使用纯 Javascript,你可以在Livescript 中构建一个顺序代码,它看起来还不错。你可能想看看this example

        # application
        do
            i = 3
            console.log td!, "start"
            <- :lo(op) ->
                console.log td!, "hi #{i}"
                i--
                <- wait-for \something
                if i is 0
                    return op! # break
                lo(op)
            <- sleep 1500ms
            <- :lo(op) ->
                console.log td!, "hello #{i}"
                i++
                if i is 3
                    return op! # break
                <- sleep 1000ms
                lo(op)
            <- sleep 0
            console.log td!, "heyy"
        
        do
            a = 8
            <- :lo(op) ->
                console.log td!, "this runs in parallel!", a
                a--
                go \something
                if a is 0
                    return op! # break
                <- sleep 500ms
                lo(op)
        

        输出:

        0ms : start
        2ms : hi 3
        3ms : this runs in parallel! 8
        3ms : hi 2
        505ms : this runs in parallel! 7
        505ms : hi 1
        1007ms : this runs in parallel! 6
        1508ms : this runs in parallel! 5
        2009ms : this runs in parallel! 4
        2509ms : hello 0
        2509ms : this runs in parallel! 3
        3010ms : this runs in parallel! 2
        3509ms : hello 1
        3510ms : this runs in parallel! 1
        4511ms : hello 2
        4511ms : heyy
        

        【讨论】:

          【解决方案9】:

          另一种看待这个问题的方法是从一个功能到另一个功能的菊花链。 拥有一个对所有被调用函数都是全局的函数数组,比如:

          arrf: [ f_final
                 ,f
                 ,another_f
                 ,f_again ],
          

          然后为您要运行的特定“f”设置一个整数数组,例如

          var runorder = [1,3,2,0];
          

          然后以'runorder'作为参数调用一个初始函数,例如 f_start(runorder);

          然后在每个函数结束时,只需将索引弹出到下一个 'f' 以执行 runorder 数组并执行它,仍然将 'runorder' 作为参数传递,但数组减一。

          var nextf = runorder.shift();
          arrf[nextf].call(runorder);
          

          很明显,这终止于一个函数,比如在索引 0 处,它不会链接到另一个函数。 这是完全确定的,避免使用“计时器”。

          【讨论】:

            【解决方案10】:

            将您的代码放在一个字符串中,迭代、评估、setTimeout 和递归以继续其余行。毫无疑问,我会改进它,或者如果它没有达到目标就扔掉它。我的目的是用它来模拟非常非常基本的用户测试。

            递归和 setTimeout 使其顺序化。

            想法?

            var line_pos = 0;
            var string =`
                console.log('123');
                console.log('line pos is '+ line_pos);
            SLEEP
                console.log('waited');
                console.log('line pos is '+ line_pos);
            SLEEP
            SLEEP
                console.log('Did i finish?');
            `;
            
            var lines = string.split("\n");
            var r = function(line_pos){
                for (i = p; i < lines.length; i++) { 
                    if(lines[i] == 'SLEEP'){
                        setTimeout(function(){r(line_pos+1)},1500);
                        return;
                    }
                    eval (lines[line_pos]);
                }
                console.log('COMPLETED READING LINES');
                return;
            }
            console.log('STARTED READING LINES');
            r.call(this,line_pos);
            

            输出

            STARTED READING LINES
            123
            124
            1 p is 0
            undefined
            waited
            p is 5
            125
            Did i finish?
            COMPLETED READING LINES
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-09-06
              • 2011-10-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-01-02
              • 2017-07-30
              相关资源
              最近更新 更多