Как заставить последовательное выполнение 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>
Реализуя обещание и связывая функции с помощью.then(), вы гарантируете, что вторая функция будет выполнена только после того, как первая будет выполнена. Это команда d.resolve() в longfunctionfirst(), которая дает сигнал для запуска следующей функция.
Технически, функция shortfunctionsecond() не должна создавать отложенное выполнение и возвращать обещание, но я влюбился в обещания и стараюсь выполнять все с обещаниями, извините.
Я опытный программист и недавно вернулся к своей старой страсти и изо всех сил стараюсь вписаться в этот объектно-ориентированный, управляемый событиями яркий новый мир, и хотя я вижу преимущества непоследовательного поведения Javascript, есть время, когда оно действительно становится на пути простоты и возможности повторного использования. Простой пример, над которым я работал, - это сделать фотографию (мобильный телефон, запрограммированный на javascript, HTML, phonegap, ...), изменить его размер и загрузить на веб-сайт. Идеальная последовательность:
- Сфотографировать
- Загрузить фотографию в элемент img
- Изменить размер картинки (используя Pixastic)
- Загрузить его на веб-сайт
- Информировать пользователя об ошибке
Все это было бы очень простой последовательной программой, если бы каждый шаг возвращал управление к следующему после его завершения, но на самом деле:
- Фотосъемка выполняется асинхронно, поэтому программа пытается загрузить ее в элемент img до ее появления.
- Загрузка фотографии выполняется асинхронно, поэтому изменение размера изображения начинается до полной загрузки IMG
- Изменение размера асинхронно, поэтому загрузка на веб-сайт начинается до полного изменения размера изображения.
- Загрузка на веб-сайт выполняется асинхронно, поэтому программа продолжается до полной загрузки фотографии.
И кстати 4 из 5 шагов включают функции обратного вызова.
Таким образом, мое решение состоит в том, чтобы вложить каждый шаг в предыдущий и использовать.onload и другие подобные уловки, это выглядит примерно так:
takeAPhoto(takeaphotocallback(photo) {
photo.onload = function () {
resizePhoto(photo, resizePhotoCallback(photo) {
uploadPhoto(photo, uploadPhotoCallback(status) {
informUserOnOutcome();
});
});
};
loadPhoto(photo);
});
(Надеюсь, я не сделал слишком много ошибок, доводя код до необходимого, реальная вещь слишком отвлекает)
Я считаю, что это идеальный пример, когда асинхронность не годится, а синхронизация хороша, потому что, в отличие от обработки событий в пользовательском интерфейсе, мы должны завершить каждый шаг до того, как будет выполнен следующий, но код представляет собой русскую кукольную конструкцию, он запутанный и нечитаемый, повторного использования кода трудно достичь, потому что из-за всей вложенности просто трудно передать во внутреннюю функцию все необходимые параметры, не передавая их в каждый контейнер по очереди или не используя злые глобальные переменные, и я бы хотел, чтобы результат всех этот код даст мне код возврата, но первый контейнер будет завершен задолго до того, как код возврата станет доступен.
Теперь вернемся к первоначальному вопросу Тома: что было бы умным, легко читаемым, легко повторно используемым решением для того, что было бы очень простой программой 15 лет назад с использованием, скажем, C и тупой электронной доски?
Требование на самом деле настолько простое, что у меня сложилось впечатление, что я, должно быть, упускаю фундаментальное понимание Javsascript и современного программирования. Конечно, технология предназначена для повышения производительности, верно?
Спасибо тебе за твое терпение
Раймон Динозавр;-)
Если вы не настаиваете на использовании чистого 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
В вашем примере первая функция действительно завершается до запуска второй функции. setTimeout не задерживает выполнение функции до тех пор, пока не истечет тайм-аут, он просто запустит таймер в фоновом режиме и выполнит ваше оповещение по истечении указанного времени.
В JavaScript нет собственного способа "спать". Вы могли бы написать цикл, который проверяет время, но это сильно напрягает клиента. Вы также можете выполнить синхронный вызов AJAX, как описано в emacsian, но это добавит дополнительную нагрузку на ваш сервер. Лучше всего избегать этого, что должно быть достаточно просто для большинства случаев, когда вы понимаете, как работает setTimeout.
У меня была такая же проблема, это мое решение:
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);
}
}
Еще один способ взглянуть на это состоит в последовательном переходе от одной функции к другой. Имейте массив функций, который является глобальным для всех вызываемых вами функций, скажем:
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, которая не связывается с другой функцией. Это полностью детерминировано, избегая "таймеров".
Поместите свой код в строку, 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);
ВЫХОД
STARTED READING LINES
123
124
1 p is 0
undefined
waited
p is 5
125
Did i finish?
COMPLETED READING LINES
Я попробовал способ обратного вызова и не смог заставить это работать, вы должны понять, что значения все еще атомарны, хотя выполнение не является. Например:
alert('1');
<--- эти две функции будут выполняться одновременно
alert('2');
<--- эти две функции будут выполняться одновременно
но поступив так, вы заставите нас знать порядок исполнения:
loop=2;
total=0;
for(i=0;i<loop;i++) {
total+=1;
if(total == loop)
alert('2');
else
alert('1');
}
В javascript нет способа заставить код ждать. У меня была эта проблема, и то, как я это сделал, было сделать синхронный SJAX-вызов к серверу, и сервер фактически выполняет спящий режим или выполняет некоторую активность перед возвратом, и все время js ждет.
Например, синхронизация AJAX: http://www.hunlock.com/blogs/Snippets:_Synchronous_AJAX