цикл ожидания в обещании никогда не выполняется после разрешения обещания? - PullRequest
0 голосов
/ 26 апреля 2018

Я столкнулся с проблемой, когда обратный вызов, отправленный на setTimeout из разрешенного обещания, никогда не будет выполнен.

Предполагается, что у меня есть следующее:

class Foo {
  constructor(foo) {
    this.foo = foo;
  }

  async execUntilStop(callback) {
    const timeoutLoopCallback = () => {
      if (this.stopExec) return;
      callback({ data: 'data' });
      setTimeout(timeoutLoopCallback, 10);
    };
    setTimeout(timeoutLoopCallback, 10);

    return { data: 'data'};
  }

  stop() {
    this.stopExec = true;
  }
}

const myFunc = async function () {
  let callbackCalled = false;
  const callback = () => callbackCalled = true;
  foo = new Foo('foo');
  foo.execUntilStop(callback);
  const hasCallbackCalled = async () => callbackCalled;

  while(!(await hasCallbackCalled())) null;
  foo.stop();
  return 'success!';
};

myFunc().then((result) => console.log(result))

myFunc() никогда не разрешается, поскольку постоянно ждет, пока callbackCalled будет true.

Что яздесь не хватает?Я считаю, что цикл обработки событий не должен блокироваться, так как я вызываю await для асинхронной функции, чтобы проверить, был ли вызван обратный вызов.Я предполагаю, что это как-то связано с тем, что timeoutLoopCallback связан с решенным обещанием, но я не эксперт по javascript и мог бы использовать некоторые отзывы.

Примечание: это выглядит немного странно, но по сути это производная откласс, который я пытаюсь написать для этого, будет постоянно выполнять обратный вызов до тех пор, пока не будет остановлен.


решено

Используя то, чему я научился у @ traktor53ответ, я написал удобную функцию денди wait:

// resolves when callback returns true
const wait = callback => new Promise((resolve, reject) => {
  const end = () => {
    try {
      if (callback()) {
        resolve(true);
      } else {
        setTimeout(end, 0);
      }
    } catch(error) {
      reject(error);
    }
  };
  setTimeout(end, 0);
});


class Foo {
  constructor(foo) {
    this.foo = foo;
  }

  async execUntilStop(callback) {
    const timeoutLoopCallback = () => {
      if (this.stopExec) return;
      callback({ data: 'data' });
      setTimeout(timeoutLoopCallback, 10);
    };
    setTimeout(timeoutLoopCallback, 10);

    return { data: 'data'};
  }

  stop() {
    this.stopExec = true;
  }
}

const myFunc = async function (num) {
  let callbackCalled = false;
  const callback = () => callbackCalled = true;
  foo = new Foo('foo');
  foo.execUntilStop(callback);

  const hasCallbackCalled = () => callbackCalled;
  await wait(hasCallbackCalled);
  foo.stop();
  return 'success!';
};

myFunc().then((result) => console.log(result)); // => success!

1 Ответ

0 голосов
/ 26 апреля 2018

Рабочие места для обработки обещания переходят в «очередь заданий на обещание» (PJQ), описанную в стандартах ECMAScript.Эта номенклатура не часто используется в документации HTML.

Браузеры (и, по крайней мере, один механизм сценариев) помещают задания для PJQ в то, что обычно называют «Микро-очередь задач» (MTQ).Диспетчер задач цикла событий проверяет MTQ при возврате из вызова сценария из цикла событий, чтобы определить, есть ли в нем какие-либо задания, и выскочит и выполнит самое старое задание в очереди, если оно есть.Строка в исходном сообщении

 while(!(await callbackCalled)) null;

(что при первом вызове эквивалентно

while( !( await Promise.resolve( false));  // callbackCalled is false

)

помещает задание для получения установленного значенияобещание, возвращаемое Promise.resolve в MTQ и продолжающее выполнение, когда оператор await возвращает выполненное значение, равное false.

, поскольку браузеры обрабатывают MTQ с более высоким приоритетом, чем задачи, сгенерированныеистечение таймера, выполнение продолжается после операции await и немедленно выполняет еще одну итерацию цикла и помещает другое задание в MTQ для ожидания значения false, без обработки каких-либо обратных вызовов таймера между .

Это устанавливает асинхронный бесконечный цикл (кстати, поздравляю, я такого еще не видел!), И в этих условиях я бы не ожидал, что обратный вызов таймера будет выполнен и вызовет timeoutLoopCallback во второй раз.

Бесконечный цикл также блокирует продолжение следующей строки:

  foo.stop()

никогда не выполняется.

Обратите внимание на блокиНаблюдаемый здесь эффект является следствием реализации «Обещания заданий» в HTML-коде. Принятый ECMAScript решил не указывать детали реализации и приоритета для реальных систем JavaScript.Так что вините стандарты HTML, а не ECMAScript: D

Также обратите внимание: замена await calledBackCalled на await hasCallbackCalled() не решит проблему - будут сгенерированы разные задания обещания, но оператор await по-прежнему вернет false.


(обновление) Поскольку вы спрашиваете, фактические шаги для
 while(!(await hasCallbackCalled())) null;

:

  1. Оценка hasCallbackCalled()
  2. 'hasCallbackCalled` является асинхроннымФункция и возвращает обещание, выполненное с возвращаемым значением тела функции.
  3. Тело функции является синхронным кодом и выполняет возвращенное обещание при первом вызове, синхронно возвращая значение callbackCalled (что составляет false)
  4. Обещание, возвращаемое асинхронной функцией, до сих пор синхронно выполнялось со значением false.
  5. await теперь добавляет обработчики, вызывая .then для обещания, полученного вшаг 4, чтобы сообщить await установленное значение и состояние (в данном случае «выполнено»).
  6. Но вызов then для выполненного обещания синхронно вставляет задание для вызова обработчика с выполненным значением в MTQ
  7. у MTQ теперь есть задание длякод вызова для этого конкретного await назад;
  8. await возвращается в диспетчер цикла событий.
  9. теперь задание MTQ выполняет обработчик then, добавленный на шаге 5,
  10. обработчик then возобновляет обработку оператора awaitкоторый возвращает значение false пользовательскому сценарию.
  11. пока цикл цикла продолжится с шага 1.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...