Подтвердить что ты не робот

Как дождаться выполнения обещания JavaScript перед возобновлением функции?

Я делаю некоторые модульные тесты. Тестовая среда загружает страницу в iFrame, а затем запускает утверждения против этой страницы. Перед началом каждого теста я создаю Promise, который устанавливает событие iFrame onload для вызова resolve(), устанавливает iFrame src и возвращает обещание.

Итак, я могу просто позвонить loadUrl(url).then(myFunc), и он будет ожидать загрузки страницы, прежде чем выполнять myFunc.

Я использую этот тип шаблонов во всех своих тестах (не только для загрузки URL-адресов), в первую очередь для того, чтобы позволить изменениям в DOM (например, имитировать нажатие кнопки и ждать, пока divs будут скрыты и показываться).

Недостатком этого дизайна является то, что я постоянно пишу анонимные функции с несколькими строками кода в них. Кроме того, пока у меня есть работа (QUnit assert.async()), тестовая функция, которая определяет promises, завершается до того, как обетование будет запущено.

Мне интересно, есть ли способ получить значение из Promise или ждать (блокировать/спящий режим) до его разрешения, аналогично .NET IAsyncResult.WaitHandle.WaitOne(). Я знаю, что JavaScript однопоточный, но я надеюсь, что это не значит, что функция не может уступить.

В сущности, есть ли способ получить следующее, чтобы выплевывать результаты в правильном порядке?

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
};

function getResultFrom(promise) {
  // todo
  return " end";
}

var promise = kickOff();
var result = getResultFrom(promise);
$("#output").append(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>
4b9b3361

Ответ 1

Мне интересно, есть ли способ получить ценность от обещания или ждать (блокировать/спящий режим) до тех пор, пока он не будет разрешен, подобно IAsyncResult.WaitHandle.WaitOne().NET. Я знаю, что JavaScript однопоточен, но я надеюсь, это не значит, что функция не может уступить.

Нынешнее поколение Javascript в браузерах не имеет wait() или sleep() что позволяет запускать другие вещи. Таким образом, вы просто не можете делать то, что вы просите. Вместо этого у него есть асинхронные операции, которые будут выполнять свою работу, а затем вызывать вас, когда они будут сделаны (как вы использовали обещания).

Часть этого из-за Javascript одинарной резьбы. Если один поток вращается, тогда никакой другой Javascript не может выполняться до тех пор, пока этот вращающийся поток не будет выполнен. ES6 представляет yield и генераторы, которые позволят использовать некоторые подобные трюки, но мы вполне можем использовать их в широком образце установленных браузеров (их можно использовать в некоторых серверных разработках, где вы управляете JS двигатель, который используется).


Тщательное управление кодом на основе обещаний может контролировать порядок выполнения для многих асинхронных операций.

Я не уверен, что я точно понимаю, какой порядок вы пытаетесь достичь в своем коде, но вы можете сделать что-то подобное с помощью существующей функции kickOff(), а затем kickOff() к нему обработчик .then() после его вызова:

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");

    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
}

kickOff().then(function(result) {
    // use the result here
    $("#output").append(result);
});

Это вернет результат в гарантированном порядке - вот так:

start
middle
end

Обновление в 2018 году (через три года после написания этого ответа):

Если вы либо transpile кода или запустить свой код в среде, которая поддерживает функцию ES7, такую как async и await, теперь вы можете использовать await, чтобы сделать ваш код "появится" ждать результата обещания. Он по-прежнему развивается с обещаниями. Он все еще не блокирует все Javascript, но это позволяет вам писать последовательные операции в более дружественном синтаксисе.

Вместо того, чтобы делать ES6:

someFunc().then(someFunc2).then(result => {
    // process result here
}).catch(err => {
    // process error here
});

Вы можете сделать это:

// returns a promise
async function wrapperFunc() {
    try {
        let r1 = await someFunc();
        let r2 = await someFunc2(r1);
        // now process r2
        return someValue;     // this will be the resolved value of the returned promise
    } catch(e) {
        console.log(e);
        throw e;      // let caller know the promise was rejected with this reason
    }
}

wrapperFunc().then(result => {
    // got final result
}).catch(err => {
    // got error
});

Ответ 2

Если вы используете ES2016, вы можете использовать async и await и сделать что-то вроде:

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())

Если вы используете ES2015, вы можете использовать Генераторы. Если вам не нравится синтаксис, вы можете абстрагировать его с помощью служебной функции async, как объяснено здесь.

Если вы используете ES5, вам, вероятно, понадобится библиотека типа Bluebird, которая даст вам больше контроля.

Наконец, если ваша среда выполнения поддерживает ES2015, порядок выполнения может быть сохранен с параллелизмом с помощью Fetch Injection.

Ответ 3

Другой вариант - использовать Promise.all, чтобы дождаться разрешения массива обещаний. В приведенном ниже коде показано, как дождаться выполнения всех обещаний, а затем обработать результаты, когда все они будут готовы (поскольку это казалось целью вопроса); Тем не менее, запуск, очевидно, может быть выведен до разрешения середины, просто добавив этот код, прежде чем он вызовет разрешение.

function kickOff() {
  let start = new Promise((resolve, reject) => resolve("start"))
  let middle = new Promise((resolve, reject) => {
    setTimeout(() => resolve(" middle"), 1000)
  })
  let end = new Promise((resolve, reject) => resolve(" end"))

  Promise.all([start, middle, end]).then(results => {
    results.forEach(result => $("#output").append(result))
  })
}

kickOff()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>

Ответ 4

Вы можете сделать это вручную. (Я знаю, что это не очень хорошее решение, но..) используйте цикл while, пока result не будет иметь значение

kickOff().then(function(result) {
   while(true){
       if (result === undefined) continue;
       else {
            $("#output").append(result);
            return;
       }
   }
});