Обновить

Комментарии 15

Чем только люди не занимаются, лишь бы не использовать async/await

async function doWork1() {
  return new Promise(resolve => {
    setTimeout(resolve, 3000)
  })
}

async function doWork2() {
  return new Promise(resolve => {
    setTimeout(resolve, 3000)
  })
}

async function main() {
  await doWork1()
  await doWork2()
}

const t1 = performance.now()
await main()
const t2 = performance.now();
console.log('script took', t2-t1, 'ms')
script took 6002.199999988079 ms

Против:

function doWork1() {
  return new Promise(resolve => {
      setTimeout(resolve, 3000)
  })
}

function doWork2() {
  return new Promise(resolve => {
      setTimeout(resolve, 3000)
  })
}

function main() {
  return Promise.all([doWork1(), doWork2()])
}

const t1 = performance.now()
main().then(() => {
  const t2 = performance.now();
  console.log('script took', t2-t1, 'ms')
})
script took 3002 ms

Чем только люди не занимаются, лишь бы не использовать async/await

Думают, наверное.

async/await превращает асинхронный код – в сихронный.

Зачем в первом случае doWork1 и doWwork2 описаны как async?

Чтобы эмулировать реальное поведение программы с использованием async/await как предлагает @Sirion.
Promise.all никак это проблему не решает, если только мы не найдем способ собрать в одном месте все функции из разных модулей которые стартуют при старте программы, и все их положить в Promise.all.

В реальности это скорее невозможно, а без async/await все достаточно просто:

class ModuleB {

  constructor() {
    this.init()
  }
  
  init() {
    fetch('someApi')
     .then(data => this.data = data);
  }
}

// moduleA.js
import { ModuleB } from './moduleB'

class ModuleA {
  moduleB = new ModuleB();
  
  init() {
    fetch('someApi')
     .then(data => this.data = data);
  }
}

new ModuleA() 
// ModuleB.init и ModuleA.init выполняются паралельно

Крутая возможность async/await - не использовать await, если не нужно дожидаться результата. Просто вызываем что нужно без await, и будет счастье

await Promise.all требуется, если мы хотим получить результаты или хотя бы дождаться момента завершения. Если мы запускаем асинхронные таски в стиле fire&forget, то нам просто не нужен await на верхнем уровне (хотя мы можем использовать async/await в самих тасках)

Чтоб не быть голословным, вот эквивалентный код

class ModuleB {

  constructor() {
    this.init()
  }
  
  async init() {
    this.data = await fetch('someApi')
  }
}

// moduleA.js
import { ModuleB } from './moduleB'

class ModuleA {
  moduleB = new ModuleB();
  
  // вы, видимо, пропустили
  constructor() {
    this.init()
  }
  
  async init() {
    this.data = await fetch('someApi')
  }
}

new ModuleA() 

Ну и в чем смысл этих await-ов, если в итогде вы пришли к моему первому примеру, только менее безопасно?

Во-первых, поправка: не менее, а более безопасно) в случае чего ошибки вывалятся нормально, а не uncaught (in promise)

Во-вторых, даже просто глазу приятнее. Нет бойлерплейта с .then и стрелками.

В-третьих, а какие именно чудеса вам должен явить async/await? Это по сути синтаксический сахар, позволяющий вместо цепочек .then и .catch (которые могут быть очень длинными, да ещё и вложенными) писать синхронно выглядящий код. Мой пойнт не в том, что с их помощью можно сделать что-то особенное, а в том, что с ними можно сделать то же, что и без них, но красивее, удобнее и ошибкобезопаснее

не менее, а более безопасно) в случае чего ошибки вывалятся нормально, а не uncaught (in promise)

class ModuleB {

  constructor() {
    this.init()
  }

  async init() {
    this.data = await fetch('someApi');
    throw new Error('surprise')
  }
}

// moduleA.js
import { ModuleB } from './moduleB'

class ModuleA {
  moduleB = new ModuleB();

  // вы, видимо, пропустили
  constructor() {
    this.init()
  }

  async init() {
    this.data = await fetch('someApi')
  }
}

new ModuleA()
Uncaught (in promise) Error: surprise

Вы просто создаете floating promises.

Да, тут наврал. Более безопасно при обработке ошибок в пределах async-функций. Но так-то и на промисы можно catch навесить.

Но в любом случае мой вариант не менее безопасен, чем исходный. Прув ми вронг)

Чего из предложений js действительно не хватает, так это принятие Temporal всеми браузерами и нодой. Как в базу пойдешь за датой рождения, так драйвер тебе дату-время дает то со смещением часового пояса, то без.

export async function toResult<T, E = unknown>(
  thunk: () => T | Promise<T>
): Promise<Result<T, E>> {
  return Promise
    .try(thunk)
    .then<Ok<T>>(value => ({ ok: true, value }))
    .catch<Err<E>>(error => ({ ok: false, error as E }));
}
export async function toResult<T, E = unknown>(
  thunk: () => T | Promise<T>
): Promise<Result<T, E>> {
  try {
    return {
      ok: true, 
      value: await thunk()
    }
  } catch (error) {
    return {
      ok: false,
      error: error as E
    }
  }
}

Адепты промисов, вы что реально какую-то красоту в первом варианте видите? От этой [censored] столько лет уходили, но с каждой волной свитчеров в js с других языков снова начинается некромантия всякого овнища 🤮

Просто перед тем как делать, надо думать.

Делайте run асинхронным. И не надо ничего маскировать. Нет ни одного случая, когда проблема описанная в начале статьи будет актуальной. Если верхнеуровневый по Dependency Inversion модуль требует синхронный интерфейс, чтобы дернуть низкоуровневый модуль, значит, сразу вызываем асинхронную инициализацию модуля низкого уровня, а потом уже дергаем верхнеуровневый модуль (прямой контроль). Либо через IoC (инвертированный контроль) по лайфсайклу все инициализируем.

По поводу обработки ошибок - обычного try/catch вместе с async/await будет достаточно в обоих случаях, если код просто синхронный и код асинхронный. Даже извращение в виде T | Promise<T> должно нормально поддерживаться, не проверял, но по идее обернуть в один try/catch обработку результата такой функции будет достаточно, если не забыть дернуть await для кейса Promise<T>.

Иначе получаем инструмент, который как будто стимулирует писать более сложный код без крайне веской на то причины. С другой стороны - бесплатно и пусть будет, мастер найдет применение.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS