70

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

<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,如果我自己无法到达任何地方,我将不得不发布一个单独的问题!

4

10 回答 10

48

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

于 2009-12-07T10:43:58.770 回答
37

这么长时间后我又回到了这个问题,因为我花了很长时间才找到我认为是一个干净的解决方案:我所知道的强制 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 来实现一切,抱歉。

于 2013-08-20T12:23:26.870 回答
12

我是编程的老手,最近又恢复了我的热情,正在努力适应这个面向对象、事件驱动的光明新世界,虽然我看到了 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 和现代编程的基本理解,技术肯定是为了提高生产力,对吧?

谢谢你的耐心

恐龙雷蒙德 ;-)

于 2010-10-22T04:37:34.607 回答
1

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

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

于 2009-12-07T10:42:38.050 回答
1

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

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

于 2009-12-07T10:50:38.033 回答
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');
}
于 2011-05-21T23:03:03.180 回答
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
于 2016-04-17T04:32:44.807 回答
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);
	}
}

于 2016-06-01T14:59:04.290 回答
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 处,它不会链接到另一个函数。这是完全确定的,避免了“计时器”。

于 2016-08-13T13:38:59.337 回答
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
于 2018-05-10T05:55:47.207 回答