Использование promises для ожидания вызванных событий

В следующем коде thing - внешний объект, который я не контролирую; Я не могу изменить, как работает система событий thing. Когда вызывается fn, мы возвращаем обещание, исполнитель которого слушает событие, а затем начинает ожидание ряда функций, которые в конечном итоге приводят к срабатыванию этого события:

function fn() {

  return new Promise(async function(resolve, reject) {

      // This handler must be attached before `c` is called
      thing.once('myEvent', function(e) {
        resolve(e.data); // done
      });

      // The order of these functions calls is important,
      // and they may produce errors that need to be handled.
      await a();
      await b();
      await c(); // this causes myEvent

  });

}

Это отлично работает, но мне сказали, что это обещание анти-шаблона и что я должен сделать fn функцию async. Как мне это сделать? Если я сделал fn функцию async, то как я могу разрешить e.data из обработчика события?

Изменить:

Я принял ответ Берги, потому что он полезен для объяснения анти-шаблона и того, как он применяется к этому сценарию. Сказав это, я думаю, что приведенный выше код более читабельен и явно показывает, что происходит, поэтому я собираюсь сохранить его как есть. Это не нуб, осуждающий лучшие практики, это просто для моего случая использования, следуя правилам, делает вещи более сложными, чем они должны быть. Конечно, это оставляет меня открытыми для определенных проблем, но мне просто нужно будет жить с этим, пока не найду лучший способ сделать это.

Ответ 1

Не делайте никаких await внутри конструктора Promise - вам нужно только выполнить обещание асинхронного обратного вызова:

async function fn() {
  await a();
  await b();
  await c(); // this causes myEvent
  return new Promise(function(resolve, reject) {
    thing.once('myEvent', function(e) {
      resolve(e.data); // done
    });
  });
}

То, что запускает процесс, который в конечном итоге приводит к отправке события, обычно также вызывается внутри функции обратного вызова исполнителя Promise (для перехвата синхронных исключений), но обычно она не возвращает обещание, как ваша функция c.

Может быть, это выражает намерение лучше:

async function fn() {
  await a();
  await b();
  const {data} = await new Promise(resolve => {
    thing.once('myEvent', resolve);
    thing.c(); // this causes myEvent
  });
  return data;
}

Конечно, это предполагает, что вам нужно только начать слушать событие, когда вы вызвали другие. Если вы ожидаете, что событие сработает до этого, у вас, по сути, будет гонка с параллельным выполнением - я бы рекомендовал использовать Promise.all в этом случае:

async function fn() {
  await a();
  await b();
  const [{data}, cResult] = await Promise.all([
    new Promise(resolve => thing.once('myEvent', resolve)),
    c()
  ]);
  return data;
}

Если у вас есть узел v11.13.0 или выше, вы можете использовать метод events.once чтобы вам не нужно было создавать обещание самостоятельно - он также правильно обрабатывает события ошибок:

import { once } from 'events';

async function fn () {
  await a()
  await b()
  await c()
  await once(thing, 'myEvent')
}