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


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

Ответы:


142

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

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!'))


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

Также в первоначальном вопросе не упоминается асинхронность, поэтому, если кто-то запутается, если ваша функция синхронная, она будет заблокирована при вызове. Например:

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

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

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

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


9
@zerkms - Хотите уточнить?
Мэтт Уэй

7
обратные вызовы неудобны для работы с асинхронностью, обещания намного проще и гибче
zerkms

1
Хотя вы правы в том, что мне не следовало использовать слово best (обновлено), удобство обратных вызовов и обещаний зависит от сложности проблемы.
Мэтт Уэй

3
Это становится оффтопом, но я лично не вижу причин предпочитать обратные вызовы в эти дни :-) Не могу вспомнить ни один из моего кода, написанного за последние 2 года, при условии, что обратные вызовы превышают обещания.
zerkms

3
Если вы хотите, вы можете использовать функцию стрелки в es6 secondFunction () {firstFunction ((response) => {console.log (response);}); }
Джанака Пушпакумара

53

Используйте async / await:

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

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

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

9
для тех из нас, кто придерживается поддержки старых браузеров, IE не поддерживает async / await
kyle

Может ли это использоваться вне обеих функций, например $(function() { await firstFunction(); secondFunction(); });,?
Льюистрик

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, не могли бы вы предоставить ссылку на jsfiddle или codepen того, что вы пробовали?
noseratio

1
Здравствуйте, спасибо за вашу заботу. Я получил это работает. Спасибо..!
vigamage

18

Элегантный способ дождаться завершения одной функции - использовать Promises с функцией async / await .


  1. Во-первых, создайте Обещание . Созданная мною функция будет завершена через 2 с. Я использовал setTimeoutдля того, чтобы продемонстрировать ситуацию, когда выполнение инструкций займет некоторое время.
  2. Для второй функции вы можете использовать функцию async / await, где вы должны 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 поможет им.
Путтамаригова 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. Загрузка div мигает, и он исчезает, в то время как вызов ajax почти не завершен, хотя makeAjaxCall()и вернулся. Осложнение состоит в том, что makeAjaxCall()его работа разбита на куски, которые постепенно продвигаются при каждом вращении основного потока JavaScript - он ведет себя асинхронно. Но тот же основной поток за один оборот / прогон выполнил синхронные части быстро и предсказуемо.

Итак, способ, которым я справился : Как я уже сказал, функция - это атомная единица работы. Я объединил код функции 1 и 2 - я поместил код функции 1 в функцию 2 перед асинхронным вызовом. Я избавился от функции 1. Все до асинхронного вызова, включая асинхронный вызов, выполняется предсказуемо, по порядку.

ТОГДА, когда асинхронный вызов завершается после нескольких вращений основного потока JavaScript, он вызывает функцию 3. Это гарантирует порядок . Например, в ajax обработчик события onreadystatechange вызывается несколько раз. Когда он сообщит о завершении, вызовите последнюю функцию, которую вы хотите.

Я согласен, что это грязнее. Мне нравится, когда код должен быть симметричным, мне нравится, когда функции выполняют одно (или близко к нему), и мне не нравится, когда вызов 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
Для downvoter (s), мне было бы интересно узнать, что не так с этим ответом.
Кермит

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>

Пожалуйста, добавьте хотя бы комментарии высокого уровня для описания данного фрагмента. Это должно объяснить, как ваш код решает проблему.
Холодный Цербер

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

0

Ваше основное веселье будет называться firstFun, а затем закончится ваше следующее веселье.

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.