Комментарии 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 превращает асинхронный код – в сихронный.
Я не уследил, в какой момент в РФ запретили await Promise.all
Зачем в первом случае 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.
es‑shims/promise.try — лёгкий шим, если не хотите тянуть весь core‑js.
Чего из предложений 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>.
Иначе получаем инструмент, который как будто стимулирует писать более сложный код без крайне веской на то причины. С другой стороны - бесплатно и пусть будет, мастер найдет применение.
Promise.try: единый вход для sync/async и единая обработка ошибок