Как заставить последовательное выполнение Javascript? - PullRequest
61 голосов
/ 07 декабря 2009

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

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

44 голосов
/ 07 декабря 2009

Ну, 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, вам придется работать с обратными вызовами.

30 голосов
/ 20 августа 2013

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

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

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

11 голосов
/ 22 октября 2010

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

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

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

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

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

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

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

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

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

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

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

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

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

1 голос
/ 01 июня 2016

У меня была такая же проблема, вот мое решение:

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 голос
/ 17 апреля 2016

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

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

alert('1'); <--- эти две функции будут выполняться одновременно </p>

alert('2'); <--- эти две функции будут выполняться одновременно </p>

но при этом вы узнаете порядок исполнения:

loop=2;
total=0;
for(i=0;i<loop;i++) {
           total+=1;
           if(total == loop)
                      alert('2');
           else
                      alert('1');
}
1 голос
/ 07 декабря 2009

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

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

1 голос
/ 07 декабря 2009

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

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

0 голосов
/ 10 мая 2018

Поместите свой код в строку, iterate, eval, setTimeout и recursion, чтобы продолжить работу с оставшимися строками. Без сомнения, я уточню это или просто выкину, если оно не достигнет цели. Я намерен использовать его для имитации действительно, действительно базового пользовательского тестирования.

Рекурсия и 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);

OUTPUT

STARTED READING LINES
123
124
1 p is 0
undefined
waited
p is 5
125
Did i finish?
COMPLETED READING LINES
0 голосов
/ 13 августа 2016

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

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, которая не связывается с другой функцией. Это полностью детерминировано, избегая «таймеров».

...