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

У меня есть две 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?

Ответ 1

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

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

Ответ 2

Кажется, вам не хватает важного момента: 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 thread- обработка и условия гонки).

Тем не менее, вы все равно можете сделать поток кода внутри 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 ключевое слово в составе generators. Это позволит "пробивать" асинхронные дыры в другом синхронном потоке кода JavaScript (подробнее и пример). Однако поддержка браузеров для генераторов в настоящее время ограничена Firefox и Chrome, AFAIK.

Ответ 3

Используйте 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
};

Ответ 4

Единственная проблема с обещаниями заключается в том, что 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 - это ведет asychronously. Но тот же основной поток за один оборот/прогон выполнил синхронные части быстро и предсказуемо.

Итак, способ, которым я справился: Как я уже сказал, функция - это атомная единица работы. Я объединил код функции 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 функции, чтобы выполнять их одну за другой?

Ответ 5

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

Быстрый пример:

let firstFunction = new Promise(function(resolve, reject) {
    let x = 10
    let y = 0
    setTimeout(function(){
      for(i=0;i<x;i++){
         y++
      }
       console.log('loop completed')  
       resolve(y)
    }, 2000)
})

Во-первых, создайте Обещание. Вышеуказанная функция будет завершена через 2 с. Я использовал setTimeout, чтобы продемонстрировать ситуацию, когда выполнение инструкций займет некоторое время (на вашем примере).

async function secondFunction(){
    let result = await firstFunction
    console.log(result)
    console.log('next step')
}; 

secondFunction()

Для второй функции вы можете использовать функцию async/await, где вы будете ждать завершения первой функции, прежде чем продолжить выполнение инструкций.

Примечание. Вы можете просто разрешить Обещание без какого-либо значения, такого как resolve(). В моем примере я разрешил Обещание значением y которое затем смогу использовать во второй функции.

Надеюсь, это поможет людям, которые все еще посещают эту ветку.