Правильний спосіб зачекати, коли одна функція закінчиться, перш ніж продовжувати?


187

У мене дві функції JS. Один дзвонить іншому. У межах функції виклику я хотів би зателефонувати іншому, зачекати, коли ця функція закінчиться, а потім продовжуйте. Так, наприклад / псевдокод:

function firstFunction(){
    for(i=0;i<x;i++){
        // do something
    }
};

function secondFunction(){
    firstFunction()
    // now wait for firstFunction to finish...
    // do something else
};

Я придумав це рішення, але не знаю, чи це розумний спосіб зробити це.

var isPaused = false;

function firstFunction(){
    isPaused = true;
    for(i=0;i<x;i++){
        // do something
    }
    isPaused = false;
};

function secondFunction(){
    firstFunction()
    function waitForIt(){
        if (isPaused) {
            setTimeout(function(){waitForIt()},100);
        } else {
            // go do that thing
        };
    }
};

Це законно? Чи є більш елегантний спосіб впоратися з цим? Можливо, з jQuery?


11
Що firstFunctionсаме робить це, щоб зробити його асинхронним? У будь-якому випадку - перевірити обіцянки
— zerkms

Перша функція - це швидке оновлення тактової шкали кожні 10-ту секунду. Що ... подумайте про це, я думаю, ми могли б попередньо обчислити, а потім просто вручну призупинити другий виклик функції через setTimeout. Однак, я все ще бачу бажання мати можливість паузи в іншому місці.
— DA.

вам не потрібна пауза - google для обіцянок в jquery
— zerkms

@zerkms обіцянки виглядає цікаво! Все ще досліджується підтримка браузера ...
— DA.

1
У мене теж така ж проблема.
— Vappor Washmade

Відповіді:


140

Один із способів впоратися з асинхронною роботою, наприклад, це використовувати функцію зворотного дзвінка, наприклад:

function firstFunction(_callback){
    // do some asynchronous work
    // and when the asynchronous stuff is complete
    _callback();    
}

function secondFunction(){
    // call first function and pass in a callback function which
    // first function runs when it has completed
    firstFunction(function() {
        console.log('huzzah, I\'m done!');
    });    
}

Згідно з пропозицією @Janaka Pushpakumara, тепер ви можете використовувати функції стрілок для досягнення того ж самого. Наприклад:

firstFunction(() => console.log('huzzah, I\'m done!'))


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

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

doSomething()
// the function below will wait until doSomething completes if it is synchronous
doSomethingElse()

Якщо хоч як мається на увазі, функція є асинхронною, то спосіб, яким я схильний мати справу з усією моєю асинхронною роботою сьогодні, - це асинхронність / очікування. Наприклад:

const secondFunction = async () => {
  const result = await firstFunction()
  // do something else here after firstFunction completes
}

IMO, асинхронізація / очікування робить ваш код набагато прочитанішим, ніж використання обіцянок безпосередньо (більшу частину часу). Якщо вам потрібно впоратися з помилками вловлювання, то використовуйте його з try / catch. Детальніше про це читайте тут: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function .


9
@zerkms - Хочете розробити?
— Matt Way


1
Хоча ви праві, що я не повинен був використовувати слово найкраще (оновлено), зручність зворотних викликів проти обіцянок залежить від складності проблеми.
— Matt Way

3
Це стає оффтопічним, але я особисто не бачу причин віддавати перевагу зворотним дзвінкам сьогодні :-) Не можу запам'ятати жоден мій код, написаний за останні 2 роки, за умови зворотних викликів над обіцянками.
— zerkms

3
Якщо ви хочете, ви можете використовувати функцію стрілки в es6 secondFunction () {firstFunction ((відповідь) => {console.log (відповідь);}); }
— Янака Пушпакумара

53

Використовувати асинхронізацію / очікувати:

async function firstFunction(){
  for(i=0;i<x;i++){
    // do something
  }
  return;
};

тоді використовуйте функцію wait в іншій функції, щоб дочекатися її повернення:

async function secondFunction(){
  await firstFunction();
  // now wait for firstFunction to finish...
  // do something else
};

9
для тих, хто з нас затримався на підтримці старих браузерів, IE не підтримує async / wait
— kyle

Чи можна це використовувати поза обома функціями, як у $(function() { await firstFunction(); secondFunction(); });,?
— Lewistrick

1
@Lewistrick Просто пам’ятайте, що awaitйого можна використовувати лише всередині asyncметоду. Тож якщо ви робите свою батьківську функцію, asyncви можете зателефонувати стільки, скільки async methodsвсередині, або без awaitтого, що хочете.
— Фаваз

Чи можливо awaitце setTimeout?
— Шаян

1
@Shayan так, так, вставте setTimeout в іншу функцію, яка вирішує обіцянку після таймауту.
— Фаваз

51

Здається, вам тут не вистачає важливого моменту: JavaScript - це однопотокове середовище виконання. Давайте ще раз подивимось на ваш код, зверніть увагу alert("Here"):

var isPaused = false;

function firstFunction(){
    isPaused = true;
    for(i=0;i<x;i++){
        // do something
    }
    isPaused = false;
};

function secondFunction(){
    firstFunction()

    alert("Here");

    function waitForIt(){
        if (isPaused) {
            setTimeout(function(){waitForIt()},100);
        } else {
            // go do that thing
        };
    }
};

Не потрібно чекати isPaused. Коли ви бачите «тут» попередження, isPausedбуде falseвже, і firstFunctionбудуть повернуті. Це тому, що ви не можете "поступитися" зсередини forциклу ( // do something), цикл може не перерватися і доведеться спочатку повністю завершити (детальніше: обробка потоками Javascript та умови гонки ).

Зважаючи на це, ви все ще можете змусити потік коду всередині firstFunctionбути асинхронним і використовувати зворотний виклик або обіцяти повідомити абонента. Вам доведеться відмовитися від forциклу і імітувати його ifзамість нього ( JSFiddle ):

function firstFunction()
{
    var deferred = $.Deferred();

    var i = 0;
    var nextStep = function() {
        if (i<10) {
            // Do something
            printOutput("Step: " + i);
            i++;
            setTimeout(nextStep, 500); 
        }
        else {
            deferred.resolve(i);
        }
    }
    nextStep();
    return deferred.promise();
}

function secondFunction()
{
    var promise = firstFunction();
    promise.then(function(result) { 
        printOutput("Result: " + result);
    });
}

З іншого боку, JavaScript 1.7 представив yieldключове слово як частину генераторів . Це дозволить "пробити" асинхронні отвори в синхронному потоці коду JavaScript ( детальніше та приклад ). Однак підтримка браузера для генераторів наразі обмежена Firefox та Chrome, AFAIK.


3
Ти врятував мене. $ .Deferred () - це те, про що я стріляв. Спасибі
— Temitayo

@noseratio Я спробував це. Але метод waitForIt взагалі не викликається. Що я роблю неправильно?
— vigamage

@vigamage, чи можете ви надати посилання на jsfiddle або codepen того, що ви спробували?
— nosratio

1
Привіт, Дякую за турботу Я працював. Спасибі..!
— vigamage

18

Елегантний спосіб чекати спочатку виконання однієї функції - використовувати Обіцянки з функцією async / wait .


  1. По-перше, створіть Обіцянку . Функція, яку я створив, буде виконана через 2 секунди. Я використовував setTimeoutдля того, щоб продемонструвати ситуацію, коли виконання інструкцій потребує певного часу.
  2. Для другої функції ви можете використовувати функцію async / wait, де ви будете awaitвиконувати першу функцію, перш ніж продовжувати виконання інструкцій.

Приклад:

    //1. Create a new function that returns a promise
    function firstFunction() {
      return new Promise((resolve, reject) => {
          let y = 0
          setTimeout(() => {
            for(i=0; i<10; i++){
               y++
            }
             console.log('loop completed')  
             resolve(y)
          }, 2000)
      })
    }
    
    //2. Create an async function
    async function secondFunction() {
        console.log('before promise call')
        //3. Await for the first function to complete
        let result = await firstFunction()
        console.log('promise resolved: ' + result)
        console.log('next step')
    }; 

    secondFunction()


Примітка:

Ви могли б просто без будь - якого значення , як так . У моєму прикладі я значення зі значенням, яке я можу потім використовувати у другій функції.resolvePromiseresolve()resolvedPromisey


так, це завжди буде працювати. якщо хтось, хто бере участь у подібних сценаріях, тоді JavaScript Promise зробить для них хитрість.
— Puttamarigowda MS

5

Єдине питання з обіцянками - IE не підтримує їх. Edge є, але IE 10 та 11 там: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (сумісність внизу)

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

Подумайте про синхронну функцію як про атомну одиницю роботи . Основний потік JavaScript виконає його повністю, у тому порядку, в якому виписки відображаються в коді.

Але киньте асинхронний дзвінок, як у наступній ситуації:

showLoadingDiv(); // function 1

makeAjaxCall(); // function 2 - contains async ajax call

hideLoadingDiv(); // function 3

Це не робить те, що ви хочете . Він миттєво виконує функцію 1, функцію 2 та функцію 3. Завантаження діва спалахує і вона пропала, в той час як виклик ajax майже не завершений, хоча makeAjaxCall()повернувся. ЗАВДАННЯ полягає в тому, що makeAjaxCall()вона розбила свою роботу на шматки, які поступово просуваються кожним закруткою основного потоку JavaScript закруткою - він веде себе асихронно. Але той самий основний потік під час одного відкручування / запуску виконував синхронні частини швидко та передбачувано.

Отже, як я впорався : Як я вже сказав, функція - це атомна одиниця роботи. Я поєднав код функції 1 і 2 - я поставив код функції 1 у функції 2, перед викликом асинхронізації. Я позбувся функції 1. Все, що стосується асинхронного виклику, виконується передбачувано, в порядку.

ТАКОЖ, коли асинхронний дзвінок завершується, після декількох обертів основного потоку JavaScript, він має функцію виклику 3. Це гарантує порядок . Наприклад, за допомогою ajax обробник подій onreadystateзмінюється викликом кілька разів. Коли він повідомляє, що він виконаний, то зателефонуйте до остаточної функції, яку ви хочете.

Я погоджуюсь, що це грязніше. Мені подобається, що код має симетричний характер, мені подобається, що функції виконують одне (або близько до нього), і мені не подобається, щоб виклик Ajax якимось чином не відповідав за показ (створюючи залежність від абонента). АЛЕ, при асинхронному виклику, вбудованому в синхронну функцію, потрібно робити компроміси, щоб гарантувати порядок виконання. І я повинен кодувати IE 10, щоб не обіцянки.

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

Для обговорення потоків JavaScript див: https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23 та https://developer.mozilla.org/en-US/docs/Web/JavaScript/ EventLoop

Також ще одне подібне, високо оцінене питання з цього приводу: Як я повинен викликати 3 функції, щоб виконувати їх одну за одною?


8
Користувачам, які користуються низовиною, мені буде цікаво дізнатися, що не так у цій відповіді.
— Керміт

0

Це те, що я придумав, оскільки мені потрібно виконати кілька операцій ланцюгом.

<button onclick="tprom('Hello Niclas')">test promise</button>

<script>
    function tprom(mess) {
        console.clear();

        var promise = new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve(mess);
            }, 2000);
        });

        var promise2 = new Promise(async function (resolve, reject) {
            await promise;
            setTimeout(function () {
                resolve(mess + ' ' + mess);
            }, 2000);
        });

        var promise3 = new Promise(async function (resolve, reject) {
            await promise2;
            setTimeout(function () {
                resolve(mess + ' ' + mess+ ' ' + mess);
            }, 2000);
        });

        promise.then(function (data) {
            console.log(data);
        });

        promise2.then(function (data) {
            console.log(data);
        });

        promise3.then(function (data) {
            console.log(data);
        });
    }

</script>

Будь ласка, додайте щонайменше jus коментарів високого рівня для опису даного фрагмента. Це пояснює, як ваш код вирішує проблему.
— Холодний Цербер

Проблема полягала у виконанні другої функції після першої. Я показую, як виконувати другу функцію після першої, а також як запустити третю функцію після другої. Я зробив три функції, щоб легко зрозуміти, який код потрібен.
— Ніклас Арнбергер

0

Ваша головна забава зателефонує спочатку Забава, а після завершення наступної розваги подзвонить.

async firstFunction() {
            const promise = new Promise((resolve, reject) => {
                for (let i = 0; i < 5; i++) {
                    // do something
                    console.log(i);
                    if (i == 4) {
                        resolve(i);
                    }
                }
            });
            const result = await promise;
        }

        second() {
            this.firstFunction().then( res => {
                // third function call do something
                console.log('Gajender here');
            });
        }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.