Як слід викликати 3 функції, щоб виконати їх одна за одною?


151

Якщо мені потрібно викликати ці функції одна за одною,

$('#art1').animate({'width':'1000px'},1000);        
$('#art2').animate({'width':'1000px'},1000);        
$('#art3').animate({'width':'1000px'},1000);        

Я знаю, що в jQuery я можу зробити щось на кшталт:

$('#art1').animate({'width':'1000px'},1000,'linear',function(){
    $('#art2').animate({'width':'1000px'},1000,'linear',function(){
        $('#art3').animate({'width':'1000px'},1000);        
    });        
});        

Але припустимо, що я не використовую jQuery і хочу зателефонувати:

some_3secs_function(some_value);        
some_5secs_function(some_value);        
some_8secs_function(some_value);        

Як я повинен викликати ці функції для виконання some_3secs_functionта ПІСЛЯ, коли цей виклик закінчується, потім виконувати some_5secs_functionта ПІСЛЯ цього виклику закінчується, а потім дзвінок some_8secs_function?

ОНОВЛЕННЯ:

Це все ще не працює:

(function(callback){
    $('#art1').animate({'width':'1000px'},1000);
    callback();
})((function(callback2){
    $('#art2').animate({'width':'1000px'},1000);
    callback2();
})(function(){
    $('#art3').animate({'width':'1000px'},1000);
}));

Три анімації запускаються одночасно

Де моя помилка?


ви маєте на увазі під час виклику функцій рівно за 3 5 та 8 секунд чи лише одну за одною?
Трас Васстон

Я думаю, ви просто не впевнені у виконанні функції синхронного та асинхронного. Я оновив свою відповідь нижче. Сподіваюся, це допомагає.
Уейн

Відповіді:


243

У Javascript є синхронні та асинхронні функції.

Синхронні функції

Більшість функцій Javascript є синхронними. Якби ви викликали кілька синхронних функцій поспіль

doSomething();
doSomethingElse();
doSomethingUsefulThisTime();

вони будуть виконувати по порядку. doSomethingElseне розпочнеться, поки doSomethingне завершиться. doSomethingUsefulThisTime, у свою чергу, не розпочнеться, поки doSomethingElseне завершиться.

Асинхронні функції

Однак асинхронна функція не чекатиме один одного. Давайте подивимось на той самий зразок коду, який був у нас вище, цього разу припускаючи, що функції асинхронні

doSomething();
doSomethingElse();
doSomethingUsefulThisTime();

Функції будуть ініціалізовані в порядку, але всі вони виконуватимуться приблизно в один і той же час. Ви не можете послідовно передбачити, хто з них закінчить перший: той, у якого трапиться найменше часу на виконання, закінчить перший.

Але іноді ви хочете, щоб функції, які асинхронно виконувати в порядку, а іноді потрібно синхронно виконувати функції асинхронно. На щастя, це можливо із зворотними дзвінками та таймаутами відповідно.

Відкликання дзвінків

Давайте припустимо , що у нас є три асинхронних функцій , які ми хочемо виконати в порядку, some_3secs_function, some_5secs_function, і some_8secs_function.

Оскільки функції можуть передаватися як аргументи в Javascript, ви можете передавати функцію як зворотний виклик, який потрібно виконати після завершення функції.

Якщо ми створимо такі функції

function some_3secs_function(value, callback){
  //do stuff
  callback();
}

тоді ви можете зателефонувати тоді, як-от так:

some_3secs_function(some_value, function() {
  some_5secs_function(other_value, function() {
    some_8secs_function(third_value, function() {
      //All three functions have completed, in order.
    });
  });
});

Часи очікування

У Javascript ви можете вказати функцію, яку потрібно виконати через певний час (у мілісекундах). Це, по суті, може змусити синхронні функції вести себе асинхронно.

Якщо у нас є три синхронні функції, ми можемо виконувати їх асинхронно за допомогою setTimeoutфункції.

setTimeout(doSomething, 10);
setTimeout(doSomethingElse, 10);
setTimeout(doSomethingUsefulThisTime, 10);

Це, однак, трохи негарно і порушує принцип DRY [wikipedia] . Ми могли це трохи очистити, створивши функцію, яка приймає масив функцій та тайм-аут.

function executeAsynchronously(functions, timeout) {
  for(var i = 0; i < functions.length; i++) {
    setTimeout(functions[i], timeout);
  }
}

Це можна назвати так:

executeAsynchronously(
    [doSomething, doSomethingElse, doSomethingUsefulThisTime], 10);

Підсумовуючи це, якщо у вас є асинхронні функції, які ви хочете виконувати синхронно, використовуйте зворотні дзвінки, а якщо у вас є синхронні функції, які ви хочете виконувати асинхронно, використовуйте тайм-аути.


7
Це не затримує функції на 3,5 та 8 секунд, як це запропоновано в прикладі, вони будуть просто працювати одна за одною.
Трасс Вастон

1
@Peter - Зачекайте, тому я заплутався. Якщо це прості синхронні дзвінки, на які трапляється кілька секунд, то навіщо нам це потрібно?
Уейн

9
@Peter - +1 для найкрасивішого, згорнутого методу, який я коли-небудь бачив для виклику трьох синхронних функцій послідовно.
Уейн

4
Дякуємо вам за те, що ми вміло пояснили різницю між функціями async та синхронізації Це так багато пояснює.
jnelson

2
Це НЕ правильно з наступних причин: (1) три тайм-аути вирішаться через 10 секунд, тому всі 3 рядки спрацьовують одночасно. (2) цей метод вимагає від вас знати тривалість заздалегідь та функції "розкладу", які відбуватимуться в майбутньому, замість того, щоб чекати попередніх функцій асинхронізації в ланцюзі, щоб вони розв'язалися, і які були тригером. --- замість цього ви хочете використовувати один із наведених відповідей, використовуючи або зворотні дзвінки, обіцянки або бібліотеку async.
zeroasterisk

37

Ця відповідь використовує promisesфункцію JavaScript ECMAScript 6стандарту. Якщо ваша цільова платформа не підтримує promises, заповніть її за допомогою PromiseJs .

Подивіться мою відповідь тут. Зачекайте, поки функція з анімаціями не закінчиться до запуску іншої функції, якщо ви хочете використовувати jQueryанімацію.

Ось як виглядатиме ваш код із ES6 Promisesта jQuery animations.

Promise.resolve($('#art1').animate({ 'width': '1000px' }, 1000).promise()).then(function(){
    return Promise.resolve($('#art2').animate({ 'width': '1000px' }, 1000).promise());
}).then(function(){
    return Promise.resolve($('#art3').animate({ 'width': '1000px' }, 1000).promise());
});

Звичайні методи також можуть бути обгорнуті Promises.

new Promise(function(fulfill, reject){
    //do something for 5 seconds
    fulfill(result);
}).then(function(result){
    return new Promise(function(fulfill, reject){
        //do something for 5 seconds
        fulfill(result);
    });
}).then(function(result){
    return new Promise(function(fulfill, reject){
        //do something for 8 seconds
        fulfill(result);
    });
}).then(function(result){
    //do something with the result
});

thenМетод виконується , як тільки Promiseзавершена. Зазвичай повернене значення functionпереданого thenзнаку передається наступному як результат.

Але якщо Promiseповернути a , наступна thenфункція чекає, поки Promiseзакінчиться виконання та отримає його результати (значення, яке передається fulfill).


Я знаю, що це корисно, але я знайшов код, який важко зрозуміти, не шукаючи прикладу реального світу, щоб дати йому деякий контекст. Я знайшов це відео на YouTube: youtube.com/watch?v=y5mltEaQxa0 - і написав джерело з відео тут drive.google.com/file/d/1NrsAYs1oaxXw0kv9hz7a6LjtOEb6x7z - / ... Є ще деякі нюанси , як улов відсутня в цей приклад, про який йдеться в подальшому. (використовуйте інший ідентифікатор у рядку getPostById () або спробуйте змінити ім’я автора, щоб воно не відповідало допису тощо)
JGFMK

20

Здається, ви не повністю оцінюєте різницю між синхронним та асинхронним виконанням функції.

Код, який ви надали у своєму оновленні, негайно виконує кожну з функцій зворотного дзвінка, що, в свою чергу, негайно запускає анімацію. Однак анімації виконуються асинхронно . Це працює так:

  1. Виконайте крок в анімації
  2. Дзвінок setTimeoutз функцією, що містить наступний крок анімації та затримку
  3. Проходить деякий час
  4. Відкликання дзвінків, надане setTimeoutвиконуваним
  5. Поверніться до кроку 1

Це триває, поки не завершиться останній крок анімації. Тим часом ваші синхронні функції вже давно виконані. Іншими словами, ваш виклик animateфункції не реально взяти 3 секунди. Ефект імітується затримками та зворотними дзвінками.

Вам потрібна черга . Усередині JQuery черзі анімації, тільки виконуючи свій зворотний виклик , як тільки його відповідної анімації завершується. Якщо ваш зворотний дзвінок запускає іншу анімацію, ефект полягає в тому, що вони виконуються послідовно.

У найпростішому випадку це еквівалентно наступному:

window.setTimeout(function() {
    alert("!");
    // set another timeout once the first completes
    window.setTimeout(function() {
        alert("!!");
    }, 1000);
}, 3000); // longer, but first

Ось загальна асинхронна функція циклічного циклу. Він буде викликати задані функції по порядку, чекаючи заданої кількості секунд між кожною.

function loop() {
    var args = arguments;
    if (args.length <= 0)
        return;
    (function chain(i) {
        if (i >= args.length || typeof args[i] !== 'function')
            return;
        window.setTimeout(function() {
            args[i]();
            chain(i + 1);
        }, 2000);
    })(0);
}    

Використання:

loop(
  function() { alert("sam"); }, 
  function() { alert("sue"); });

Ви, очевидно, можете змінити це, щоб прийняти настроювані часи очікування або негайно виконати першу функцію або зупинити виконання, коли функція в ланцюзі повертається, falseабо до applyфункцій у визначеному контексті чи будь-що інше, що вам може знадобитися.


14

Я вірю, що бібліотека async надасть вам дуже елегантний спосіб зробити це. Незважаючи на те, що обіцянки та зворотні дзвінки можуть трохи важко перемикати, асинхронізація може дати чіткі моделі для впорядкування вашої думки. Щоб послідовно запускати функції, вам потрібно було б помістити їх у водоспад асинхронізу . У асинхронному лінгві кожна функція називається a, taskяка приймає деякі аргументи та a callback; яка є наступною функцією в послідовності. Основна структура виглядала б приблизно так:

async.waterfall([
  // A list of functions
  function(callback){
      // Function no. 1 in sequence
      callback(null, arg);
  },
  function(arg, callback){
      // Function no. 2 in sequence
      callback(null);
  }
],    
function(err, results){
   // Optional final callback will get results for all prior functions
});

Я просто спробував коротко пояснити тут структуру. Прочитайте посібник з водоспаду для отримання додаткової інформації, він досить добре написаний.


1
Це дійсно робить JS трохи більш терпимим.
Майк

9

ваші функції повинні приймати функцію зворотного дзвінка, яка дзвонить, коли вона закінчується.

function fone(callback){
...do something...
callback.apply(this,[]);

}

function ftwo(callback){
...do something...
callback.apply(this,[]);
}

тоді використання буде таким:

fone(function(){
  ftwo(function(){
   ..ftwo done...
  })
});

4
asec=1000; 

setTimeout('some_3secs_function("somevalue")',asec*3);
setTimeout('some_5secs_function("somevalue")',asec*5);
setTimeout('some_8secs_function("somevalue")',asec*8);

Я не буду заглиблюватися в SetTimeout тут, але:

  • у цьому випадку я додав код для виконання у вигляді рядка. це найпростіший спосіб ввести var у вашу функцію setTimeout-ed, але пуристи будуть скаржитися.
  • Ви також можете передавати ім'я функції без лапок, але змінна не може бути передана.
  • ваш код не чекає запуску setTimeout.
  • Це може бути важко обернути голову спочатку: через попередній момент, якщо ви передасте змінну з вашої функції виклику, ця змінна більше не буде існувати до моменту запуску тайм-ауту - функція виклику буде виконана, і це vars пішли.
  • Мені відоме використання анонімних функцій, щоб обійти все це, але міг бути кращий спосіб,

3

Оскільки ви позначили його за допомогою JavaScript, я б перейшов до керування таймером, оскільки назви ваших функцій складають 3, 5 та 8 секунд. Отже, запускайте таймер, тривалість 3 секунди, зателефонуйте першому, 5 секунд - другим, 8 секунд - третім, після закінчення таймера зупиніть.

Зазвичай у Javascript те, що у вас є правильним для функцій, виконуються одна за одною, але оскільки, схоже, ви намагаєтеся зробити тимчасову анімацію, таймер буде найкращим варіантом.


2

//sample01
(function(_){_[0]()})([
	function(){$('#art1').animate({'width':'10px'},100,this[1].bind(this))},
	function(){$('#art2').animate({'width':'10px'},100,this[2].bind(this))},
	function(){$('#art3').animate({'width':'10px'},100)},
])

//sample02
(function(_){_.next=function(){_[++_.i].apply(_,arguments)},_[_.i=0]()})([
	function(){$('#art1').animate({'width':'10px'},100,this.next)},
	function(){$('#art2').animate({'width':'10px'},100,this.next)},
	function(){$('#art3').animate({'width':'10px'},100)},
]);

//sample03
(function(_){_.next=function(){return _[++_.i].bind(_)},_[_.i=0]()})([
	function(){$('#art1').animate({'width':'10px'},100,this.next())},
	function(){$('#art2').animate({'width':'10px'},100,this.next())},
	function(){$('#art3').animate({'width':'10px'},100)},
]);


Чи можете ви поясніть, що це? Що пов'язане з підкресленням? Що виконує призначена функція next?
mtso

1
Я пояснюю зразок 2 за допомогою jsfiddle. jsfiddle.net/mzsteyuy/3 Якщо ви дозволяєте мені приблизно пояснити, зразок 2 - це короткий шлях коду в jsfiddle. підкреслення - це масив, елементи якого є валідними (i) і функціонують далі та функціонують [0] ~ [2].
yuuya

1

Ви також можете використати обіцянки таким чином:

    some_3secs_function(this.some_value).then(function(){
       some_5secs_function(this.some_other_value).then(function(){
          some_8secs_function(this.some_other_other_value);
       });
    });

Вам доведеться зробити some_valueглобальним, щоб отримати доступ до нього зсередини

Крім того, із зовнішньої функції ви могли б повернути значення, яке використовувала б внутрішня функція, наприклад:

    one(some_value).then(function(return_of_one){
       two(return_of_one).then(function(return_of_two){
          three(return_of_two);
       });
    });

0

Я використовую функцію 'waitUntil', засновану на setTimeout JavaScript

/*
    funcCond : function to call to check whether a condition is true
    readyAction : function to call when the condition was true
    checkInterval : interval to poll <optional>
    timeout : timeout until the setTimeout should stop polling (not 100% accurate. It was accurate enough for my code, but if you need exact milliseconds, please refrain from using Date <optional>
    timeoutfunc : function to call on timeout <optional>
*/
function waitUntil(funcCond, readyAction, checkInterval, timeout, timeoutfunc) {
    if (checkInterval == null) {
        checkInterval = 100; // checkinterval of 100ms by default
    }
    var start = +new Date(); // use the + to convert it to a number immediatly
    if (timeout == null) {
        timeout = Number.POSITIVE_INFINITY; // no timeout by default
    }
    var checkFunc = function() {
        var end = +new Date(); // rough timeout estimations by default

        if (end-start > timeout) {
            if (timeoutfunc){ // if timeout function was defined
                timeoutfunc(); // call timeout function
            }
        } else {
            if(funcCond()) { // if condition was met
                readyAction(); // perform ready action function
            } else {
                setTimeout(checkFunc, checkInterval); // else re-iterate
            }
        }
    };
    checkFunc(); // start check function initially
};

Це спрацювало б ідеально, якби ваші функції встановили певну умову, яку б ви могли опитувати. Крім того, він постачається з тайм-аутами, який пропонує вам альтернативи, якщо ваша функція не змогла щось зробити (навіть у межах діапазону. Подумайте про відгуки користувачів!)

напр

doSomething();
waitUntil(function() { return doSomething_value===1;}, doSomethingElse);
waitUntil(function() { return doSomethingElse_value===1;}, doSomethingUseful);

Примітки

Дата спричиняє приблизні прогнози часу. Для більшої точності перейдіть до таких функцій, як console.time (). Зверніть увагу, що Date пропонує більшу підтримку між браузерами та застарілою підтримкою. Якщо вам не потрібні точні мілісекундні вимірювання; не турбуйтеся, або, альтернативно, не завертайте його та не пропонуйте console.time (), коли браузер підтримує це


0

Якщо метод 1 повинен бути виконаний після методу 2, 3, 4. Наступний фрагмент коду може бути вирішенням цього за допомогою відкладеного об’єкта в JavaScript.

function method1(){
  var dfd = new $.Deferred();
     setTimeout(function(){
     console.log("Inside Method - 1"); 
     method2(dfd);	 
    }, 5000);
  return dfd.promise();
}

function method2(dfd){
  setTimeout(function(){
   console.log("Inside Method - 2"); 
   method3(dfd);	
  }, 3000);
}

function method3(dfd){
  setTimeout(function(){
   console.log("Inside Method - 3"); 	
   dfd.resolve();
  }, 3000);
}

function method4(){   
   console.log("Inside Method - 4"); 	
}

var call = method1();

$.when(call).then(function(cb){
  method4();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.