Как заставить последовательное выполнение Javascript?

Я нашел довольно сложные ответы, включающие классы, обработчики событий и обратные вызовы (которые кажутся мне несколько кувалдой). Я думаю, что обратные вызовы могут быть полезны, но я не могу применить их в простейшем контексте. См. этот пример:

<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>

в этом случае вторая функция завершается перед первой функцией; каков самый простой способ (или есть один?) чтобы заставить вторую функцию отложить выполнение до тех пор, пока первая функция не будет полная?

---редактировать---

Так что это был пример мусора, но благодаря Дэвиду Хедлунду я вижу с этим новым примером, что это действительно синхронно (наряду с сбоем моего браузера в процессе тестирования!):

<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, мне придется опубликовать отдельный вопрос об этом, если я сам не могу никуда попасть!

10 ответов


Ну setTimeout, согласно его определению, не будет удерживать поток. Это желательно, потому что если бы это было так, он заморозил бы весь пользовательский интерфейс на время ожидания. если вам действительно нужно использовать 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, не задерживать поток пользовательского интерфейса, пока он не закончит. С AJAX, как и с setTimeout, вам придется работать с ответами.


Я вернулся к этим вопросам после всего этого времени, потому что мне потребовалось много времени, чтобы найти то, что я считаю чистым решением : Единственный способ заставить последовательное выполнение javascript, о котором я знаю, - это использовать обещания. Есть исчерпывающие объяснения обещаний по адресу: Обещания / A и Обещания/A+

единственная библиотека, реализующая обещания, которые я знаю, - это jquery, поэтому вот как я бы решил вопрос, используя обещания jquery :

<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>

путем реализации обещания и цепочки функций С.затем () вы гарантируете, что вторая функция будет выполнена только после выполнения первой Это команда d.resolve () в longfunctionfirst (), которые дают сигнал для запуска следующей функции.

технически shortfunctionsecond () не нужно создавать отложенное и возвращать обещание, но я влюбился в обещания и, как правило, реализую все с обещаниями, извините.


Я старый специалист по программированию и недавно вернулся к своей старой страсти и изо всех сил пытаюсь вписаться в этот объектно-ориентированный, управляемый событиями яркий новый мир, и хотя я вижу преимущества непоследовательного поведения Javascript, есть время, когда он действительно мешает простоте и повторному использованию. Простой пример, над которым я работал, - это фотография (мобильный телефон, запрограммированный на javascript, HTML, phonegap,...), изменить размер и загрузить его на веб-сайте. Идеальная последовательность :

  1. фотосъемка
  2. загрузите фотографию в элемент img
  3. изменение размера изображения (с помощью Pixastic)
  4. загрузить его на веб-сайте
  5. сообщите пользователю о неудаче успеха

все это было бы очень простой последовательной программой, если бы каждый шаг возвращал управление следующему, когда он будет завершен, но на самом деле:

  1. сфотографировать асинхронно, поэтому программа пытается загрузить его в элемент img до его существования
  2. загрузка фотографии асинхронна, поэтому изменение размера изображения начинается до полной загрузки img
  3. Resize является асинхронным, поэтому загрузка на веб-сайт начинается до того, как изображение будет полностью изменено
  4. загрузить на веб-сайт asyn, поэтому программа продолжит работу до полной загрузки фотографии.

и btw 4 из 5 шагов включают функции обратного вызова.

мое решение, таким образом, заключается в том, чтобы вложить каждый шаг в предыдущий и использовать .onload и другие подобные стратагемы, это выглядит примерно так:

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

(надеюсь, я не сделал слишком много ошибок, приведя код к тому, что важно, что реальная вещь просто слишком отвлекает)

Я считаю, что это идеальный пример, когда асинхронность не хороша, а синхронизация хороша, потому что в отличие от обработки событий пользовательского интерфейса мы должны иметь каждый шаг, прежде чем будет выполнен следующий, но код-это русская кукольная конструкция, она запутана и нечитабельна, код повторное использование трудно достичь из-за всех вложенности просто трудно привести к внутренней функции все необходимые параметры, не передавая их в каждый контейнер по очереди или используя злые глобальные переменные, и я бы хотел, чтобы результат всего этого кода дал мне код возврата, но первый контейнер будет завершен задолго до того, как код возврата будет доступен.

теперь, чтобы вернуться к первоначальному вопросу Тома, что было бы умным, легко читать, легко повторное решение того, что было бы очень простой программой 15 лет назад, используя, скажем, C и тупую электронную плату ?

требование на самом деле настолько просто, что у меня создается впечатление, что мне не хватает фундаментального понимания Javsascript и современного программирования, конечно, технология предназначена для повышения производительности ?.

Спасибо за ваше терпение

Раймон динозавр ;-)


в javascript нет способа заставить код ждать. У меня была эта проблема, и то, как я это сделал, было синхронным вызовом SJAX на сервер, и сервер фактически выполняет сон или выполняет некоторые действия перед возвращением, и все время JS ждет.

например, синхронизация AJAX:http://www.hunlock.com/blogs/Snippets:_Synchronous_AJAX


в вашем примере первая функция фактически завершается до запуска второй функции. setTimeout не удерживает выполнение функции до тех пор, пока не будет достигнут тайм-аут, он просто запустит таймер в фоновом режиме и выполнит вашу инструкцию alert После указанного времени.

в JavaScript нет собственного способа выполнения "сна". Вы можете написать цикл, который проверяет время, но это поставит большую нагрузку на клиента. Вы также можете сделать Синхронный вызов AJAX, как описал emacsian, но это поставит дополнительную нагрузку на ваш сервер. Лучше всего действительно избежать этого, что должно быть достаточно просто для большинства случаев, как только вы поймете, как работает setTimeout.


я попробовал обратный вызов и не смог заставить это работать, вы должны понять, что значения по-прежнему атомарны, хотя выполнение не является. Например:

alert('1');

alert('2');

но такие действия заставят нас узнать порядок исполнения:

loop=2;
total=0;
for(i=0;i<loop;i++) {
           total+=1;
           if(total == loop)
                      alert('2');
           else
                      alert('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);
	}
}

Если вы не настаиваете на использовании чистого 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

еще один способ взглянуть на это по цепочке от одной функции к другой. Имейте массив функций, который является глобальным для всех ваших вызываемых функций, скажем:

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

затем настройте массив целых чисел для конкретных "f", которые вы хотите запустить, e.g

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

затем вызовите начальную функцию с параметром 'runorder', например f_start (runorder);

затем в конце каждой функции просто вставьте индекс в следующий "f", чтобы выполнить runorder массив и выполнить его, все еще передавая "runorder" в качестве параметра, но с массивом, уменьшенным на единицу.

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

очевидно, что это заканчивается функцией, скажем, при индексе 0, которая не цепляется за другую функцию. Это полностью детерминировано, избегая "таймеров".


поместите свой код в строку, повторите, eval, 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