如何强制顺序执行Javascript?


68

我只发现了涉及类,事件处理程序和回调的相当复杂的答案(在我看来,这似乎是一种大锤方法)。我认为回调可能有用,但我似乎无法在最简单的上下文中应用这些回调。请参阅以下示例:

<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,所以如果我自己什么也找不到的话,我将不得不另外提出一个问题!


Answers:


47

好吧,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,您必须使用回调。


4
另外,我应该注意,AJAX请求通常是异步的,但可以使其成为同步的。
贾斯汀·约翰逊

@贾斯汀:是的,这是正确的一点,谢谢你的评论。如果问题的真实案例确实与ajax相关,则可以使用同步回调解决方案
David Hedlund

(尽管异步回调方法仍然可以提供更大的控制力和执行流的可读性)
David Hedlund

他没有要求提问AJAX
克里斯·S,2009年

1
@SukanyaPai:不,此答案写于10年前,用于解释传递回调的特定模式。在您的代码中,shortfunctionsecond将在调用它时首先调用它,而不仅仅是将其作为参数传递。的结果shortfunctionsecond(thirdFunction)将传递到longfunctionfirst。处理您介绍的复杂性的最新答案是使用Promises
David Hedlund

36

一直以来,我都回到了这个问题,因为花了我很长时间才找到我认为是干净的解决方案:强制执行我所知道的javascript顺序执行的唯一方法是使用promises。在/ 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>

通过实现promise并使用.then()链接函数,可以确保仅在第一个函数执行后才执行第二个函数。longfunctionfirst()中的命令d.resolve()发出启动下一个信号的信号。功能。

从技术上说,shortfunctionsecond()不需要创建一个deferd并返回一个promise,但是抱歉,我爱上了promise,并且倾向于实现所有带有promise的东西。


实际上承诺是当前(2015年)的EcmaScript草案的一部分:people.mozilla.org/~jorendorff/...
sjas

人们还在读这个旧答案吗?谢谢,是的,JavaScript还在继续发展,只是在等待2015年草案赶上所有的浏览器:-)
Raymond

2
大家好,我总是惊讶于这种旧花纹仍然引起人们的注意。是的,您说得对,现在ES6和越来越多的框架实现了承诺。这是陈旧的东西,但是5年前它使我望而却步,这是我在计算领域学到的最难的教训。简而言之,我被冯·纽曼(Von Newmann)对计算的顺序理解所束缚,无法掌握Javascript所支持的事件驱动开发的美丽。
雷蒙德

11

我是编程的老手,最近又回到了以前的热情,并努力适应这个面向对象,事件驱动的崭新世界,而我看到了Java的非顺序行为的优点,有时它确实会有所发展以简单和可重用的方式。我处理过的一个简单示例是拍照(使用javascript,HTML,phonegap等编程的手机),调整大小并将其上传到网站上。理想的顺序是:

  1. 拍张照片
  2. 将照片加载到img元素中
  3. 调整图片大小(使用像素)
  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事件处理相反,我们必须在执行下一个步骤之前完成每个步骤,但是代码是俄国玩偶的构造,令人困惑且难以理解,代码的可重用性很难实现,因为所有的嵌套都很难将所需的所有参数带到内部函数,而又不将它们依次传递到每个容器或使用有害的全局变量,所以我很喜欢所有的结果这个代码会给我一个返回代码,但是第一个容器将在返回代码可用之前完成。

现在回到汤姆最初的问题,对于15年前使用C和笨拙的电子板的非常简单的程序,什么是智能,易读,易重用的解决方案?

需求实际上是如此简单,以至于我有一种印象,那就是我必须对Javsascript和现代编程缺乏基本的了解,当然,技术的意图是提高生产率吗?

谢谢你的耐心

恐龙雷蒙德;-)


3
我在使用phonegap时遇到了同样的困难。我有一个充满嵌套回调函数的代码,很难阅读。在某些时候,我希望我本机实现了该应用程序。我正在使用录音机,并与服务器同步记录的数据。我试图用requirejs和骨干网简化我的代码。这有助于将事情分解并组织模块中的代码,但是我仍然需要嵌套回调...
Thomas Solti 2013年

1

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

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


2
SJAX通常是设计不良的标志,我鼓励新手避免使用它,直到他们了解其含义。
贾斯汀·约翰逊2009年

1

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

在JavaScript中,没有本地方法可以使“睡眠”。您可以编写一个循环来检查时间,但这会对客户端造成很大的压力。如emacsian所述,您也可以进行Synchronous AJAX调用,但这会给服务器增加额外的负载。最好的选择是避免这种情况,一旦您了解了setTimeout的工作原理,对于大多数情况而言,这应该足够简单。


1

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

alert('1'); <-这两个功能将同时执行

alert('2'); <-这两个功能将同时执行

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

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

1

我有同样的问题,这是我的解决方案:

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);
	}
}


1

如果你不坚持使用纯JavaScript,你可以建立一个顺序代码为LiveScript,它看起来相当不错。您可能想看一下这个例子

# 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

0

另一种看待此问题的方法是将一个功能链接到另一个功能。具有对所有调用的函数都是全局的函数,例如:

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处,该函数不会链接到另一个函数。这是完全确定性的,避免了“计时器”。


0

将您的代码放入字符串,进行迭代,评估,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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.