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


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.