До TypeScript 4.5 тип Promise<Promise<string>> не разворачивался автоматически. Awaited<T> решает эту проблему:
type A = Awaited<Promise<string>> // string
type B = Awaited<Promise<Promise<number>>> // number — рекурсивно!
type C = Awaited<string> // string — не Promise, возвращает как есть
type D = Awaited<Promise<string | number>> // string | number// Упрощённая реализация из TypeScript source:
type Awaited<T> =
T extends null | undefined ? T :
T extends object & { then(onfulfilled: infer F, ...args: infer _): any }
? F extends (value: infer V, ...args: infer _) => any
? Awaited<V> // рекурсивно разворачиваем
: never
: TAsync функция всегда возвращает Promise. TypeScript автоматически оборачивает:
// TypeScript знает что async функция возвращает Promise<T>
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
// Тип результата:
type FetchUserResult = Awaited<ReturnType<typeof fetchUser>>
// = User (не Promise<User>!)// TypeScript правильно типизирует Promise.all через кортежи:
const [user, posts, comments] = await Promise.all([
fetchUser(1), // Promise<User>
fetchPosts(1), // Promise<Post[]>
fetchComments(1) // Promise<Comment[]>
])
// user: User, posts: Post[], comments: Comment[]
// Promise.allSettled — оборачивает в PromiseSettledResult
const results = await Promise.allSettled([fetchUser(1), fetchPosts(1)])
// PromiseSettledResult<User>[] | PromiseSettledResult<Post[]>[]// Тип, который может быть значением или промисом значения
type MaybePromise<T> = T | Promise<T>
async function normalize<T>(value: MaybePromise<T>): Promise<T> {
return value // TypeScript понимает что await T | Promise<T> = T
}
// Извлечение типа из MaybePromise:
type Resolved<T> = T extends Promise<infer R> ? R : T
type A = Resolved<Promise<string>> // string
type B = Resolved<number> // number
// Async версия функции:
type AsyncVersion<F extends (...args: any[]) => any> =
(...args: Parameters<F>) => Promise<ReturnType<F>>// Паттерн Result для async операций
type AsyncResult<T> = Promise<{ data: T; error: null } | { data: null; error: Error }>
async function safeAsync<T>(promise: Promise<T>): AsyncResult<T> {
try {
const data = await promise
return { data, error: null }
} catch (err) {
return { data: null, error: err instanceof Error ? err : new Error(String(err)) }
}
}
const { data, error } = await safeAsync(fetchUser(1))
if (error) {
console.error(error.message)
} else {
console.log(data.name) // TypeScript знает: data — User
}Утилиты для типобезопасной работы с промисами: safeAsync, retry, timeout, промис-пул
// Показываем паттерны типизированной работы с промисами в JS
// --- safeAsync: Result-паттерн для промисов ---
async function safeAsync(promise) {
try {
const data = await promise
return { data, error: null }
} catch (err) {
return { data: null, error: err instanceof Error ? err : new Error(String(err)) }
}
}
// --- retry: повторяет промис N раз ---
async function retry(fn, attempts = 3, delayMs = 100) {
let lastError
for (let i = 0; i < attempts; i++) {
try {
return await fn()
} catch (err) {
lastError = err
if (i < attempts - 1) {
await new Promise(r => setTimeout(r, delayMs * (i + 1)))
console.log(`[retry] попытка ${i + 2} из ${attempts}`)
}
}
}
throw lastError
}
// --- timeout: отменяет промис через N мс ---
async function withTimeout(promise, ms) {
let timeoutId
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new Error(`Превышено время ожидания (${ms}мс)`))
}, ms)
})
try {
return await Promise.race([promise, timeoutPromise])
} finally {
clearTimeout(timeoutId)
}
}
// --- allSettledTyped: обрабатываем результаты allSettled ---
async function allSettled(promises) {
const results = await Promise.allSettled(promises)
return results.reduce((acc, result, i) => {
if (result.status === 'fulfilled') {
acc.fulfilled.push({ index: i, value: result.value })
} else {
acc.rejected.push({ index: i, reason: result.reason })
}
return acc
}, { fulfilled: [], rejected: [] })
}
// --- Имитация API ---
let fetchAttempt = 0
function mockFetch(url, failTimes = 0) {
return new Promise((resolve, reject) => {
setTimeout(() => {
fetchAttempt++
if (fetchAttempt <= failTimes) {
reject(new Error(`Сеть недоступна (попытка ${fetchAttempt})`))
} else {
resolve({ id: 1, name: 'Алексей', email: 'alex@mail.ru', url })
}
}, 50)
})
}
// --- Демонстрация ---
async function main() {
console.log('=== safeAsync: безопасная обёртка ===')
const { data, error } = await safeAsync(mockFetch('/api/user'))
if (error) {
console.log('Ошибка:', error.message)
} else {
console.log('Пользователь:', data.name)
}
const { data: d2, error: e2 } = await safeAsync(Promise.reject(new Error('404')))
console.log('Ошибка (ожидаемая):', e2.message)
console.log('data при ошибке:', d2)
console.log('\n=== retry: повтор при ошибке ===')
fetchAttempt = 0 // сброс счётчика
try {
const user = await retry(() => mockFetch('/api/users', 2), 3, 50)
console.log('Получен после retry:', user.name)
} catch (e) {
console.log('Все попытки исчерпаны:', e.message)
}
console.log('\n=== timeout: ограничение времени ===')
try {
const slow = new Promise(r => setTimeout(() => r('медленный результат'), 300))
await withTimeout(slow, 100) // таймаут 100мс < 300мс
} catch (e) {
console.log('Таймаут:', e.message)
}
const fast = new Promise(r => setTimeout(() => r('быстрый результат'), 10))
const result = await withTimeout(fast, 200) // таймаут 200мс > 10мс
console.log('До таймаута:', result)
console.log('\n=== Promise.allSettled — группируем результаты ===')
const responses = await allSettled([
Promise.resolve({ id: 1, name: 'Alice' }),
Promise.reject(new Error('Пользователь не найден')),
Promise.resolve({ id: 3, name: 'Charlie' }),
Promise.reject(new Error('Сеть недоступна')),
])
console.log('Успешно:', responses.fulfilled.map(r => r.value.name))
console.log('С ошибками:', responses.rejected.map(r => r.reason.message))
}
main()До TypeScript 4.5 тип Promise<Promise<string>> не разворачивался автоматически. Awaited<T> решает эту проблему:
type A = Awaited<Promise<string>> // string
type B = Awaited<Promise<Promise<number>>> // number — рекурсивно!
type C = Awaited<string> // string — не Promise, возвращает как есть
type D = Awaited<Promise<string | number>> // string | number// Упрощённая реализация из TypeScript source:
type Awaited<T> =
T extends null | undefined ? T :
T extends object & { then(onfulfilled: infer F, ...args: infer _): any }
? F extends (value: infer V, ...args: infer _) => any
? Awaited<V> // рекурсивно разворачиваем
: never
: TAsync функция всегда возвращает Promise. TypeScript автоматически оборачивает:
// TypeScript знает что async функция возвращает Promise<T>
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
// Тип результата:
type FetchUserResult = Awaited<ReturnType<typeof fetchUser>>
// = User (не Promise<User>!)// TypeScript правильно типизирует Promise.all через кортежи:
const [user, posts, comments] = await Promise.all([
fetchUser(1), // Promise<User>
fetchPosts(1), // Promise<Post[]>
fetchComments(1) // Promise<Comment[]>
])
// user: User, posts: Post[], comments: Comment[]
// Promise.allSettled — оборачивает в PromiseSettledResult
const results = await Promise.allSettled([fetchUser(1), fetchPosts(1)])
// PromiseSettledResult<User>[] | PromiseSettledResult<Post[]>[]// Тип, который может быть значением или промисом значения
type MaybePromise<T> = T | Promise<T>
async function normalize<T>(value: MaybePromise<T>): Promise<T> {
return value // TypeScript понимает что await T | Promise<T> = T
}
// Извлечение типа из MaybePromise:
type Resolved<T> = T extends Promise<infer R> ? R : T
type A = Resolved<Promise<string>> // string
type B = Resolved<number> // number
// Async версия функции:
type AsyncVersion<F extends (...args: any[]) => any> =
(...args: Parameters<F>) => Promise<ReturnType<F>>// Паттерн Result для async операций
type AsyncResult<T> = Promise<{ data: T; error: null } | { data: null; error: Error }>
async function safeAsync<T>(promise: Promise<T>): AsyncResult<T> {
try {
const data = await promise
return { data, error: null }
} catch (err) {
return { data: null, error: err instanceof Error ? err : new Error(String(err)) }
}
}
const { data, error } = await safeAsync(fetchUser(1))
if (error) {
console.error(error.message)
} else {
console.log(data.name) // TypeScript знает: data — User
}Утилиты для типобезопасной работы с промисами: safeAsync, retry, timeout, промис-пул
// Показываем паттерны типизированной работы с промисами в JS
// --- safeAsync: Result-паттерн для промисов ---
async function safeAsync(promise) {
try {
const data = await promise
return { data, error: null }
} catch (err) {
return { data: null, error: err instanceof Error ? err : new Error(String(err)) }
}
}
// --- retry: повторяет промис N раз ---
async function retry(fn, attempts = 3, delayMs = 100) {
let lastError
for (let i = 0; i < attempts; i++) {
try {
return await fn()
} catch (err) {
lastError = err
if (i < attempts - 1) {
await new Promise(r => setTimeout(r, delayMs * (i + 1)))
console.log(`[retry] попытка ${i + 2} из ${attempts}`)
}
}
}
throw lastError
}
// --- timeout: отменяет промис через N мс ---
async function withTimeout(promise, ms) {
let timeoutId
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new Error(`Превышено время ожидания (${ms}мс)`))
}, ms)
})
try {
return await Promise.race([promise, timeoutPromise])
} finally {
clearTimeout(timeoutId)
}
}
// --- allSettledTyped: обрабатываем результаты allSettled ---
async function allSettled(promises) {
const results = await Promise.allSettled(promises)
return results.reduce((acc, result, i) => {
if (result.status === 'fulfilled') {
acc.fulfilled.push({ index: i, value: result.value })
} else {
acc.rejected.push({ index: i, reason: result.reason })
}
return acc
}, { fulfilled: [], rejected: [] })
}
// --- Имитация API ---
let fetchAttempt = 0
function mockFetch(url, failTimes = 0) {
return new Promise((resolve, reject) => {
setTimeout(() => {
fetchAttempt++
if (fetchAttempt <= failTimes) {
reject(new Error(`Сеть недоступна (попытка ${fetchAttempt})`))
} else {
resolve({ id: 1, name: 'Алексей', email: 'alex@mail.ru', url })
}
}, 50)
})
}
// --- Демонстрация ---
async function main() {
console.log('=== safeAsync: безопасная обёртка ===')
const { data, error } = await safeAsync(mockFetch('/api/user'))
if (error) {
console.log('Ошибка:', error.message)
} else {
console.log('Пользователь:', data.name)
}
const { data: d2, error: e2 } = await safeAsync(Promise.reject(new Error('404')))
console.log('Ошибка (ожидаемая):', e2.message)
console.log('data при ошибке:', d2)
console.log('\n=== retry: повтор при ошибке ===')
fetchAttempt = 0 // сброс счётчика
try {
const user = await retry(() => mockFetch('/api/users', 2), 3, 50)
console.log('Получен после retry:', user.name)
} catch (e) {
console.log('Все попытки исчерпаны:', e.message)
}
console.log('\n=== timeout: ограничение времени ===')
try {
const slow = new Promise(r => setTimeout(() => r('медленный результат'), 300))
await withTimeout(slow, 100) // таймаут 100мс < 300мс
} catch (e) {
console.log('Таймаут:', e.message)
}
const fast = new Promise(r => setTimeout(() => r('быстрый результат'), 10))
const result = await withTimeout(fast, 200) // таймаут 200мс > 10мс
console.log('До таймаута:', result)
console.log('\n=== Promise.allSettled — группируем результаты ===')
const responses = await allSettled([
Promise.resolve({ id: 1, name: 'Alice' }),
Promise.reject(new Error('Пользователь не найден')),
Promise.resolve({ id: 3, name: 'Charlie' }),
Promise.reject(new Error('Сеть недоступна')),
])
console.log('Успешно:', responses.fulfilled.map(r => r.value.name))
console.log('С ошибками:', responses.rejected.map(r => r.reason.message))
}
main()Реализуй асинхронную функцию `fetchWithCache(url, fetcher, ttlMs)`. Она должна: при первом вызове с данным url — вызвать `fetcher(url)`, закэшировать результат с временной меткой; при повторном вызове в течение `ttlMs` миллисекунд — вернуть кэшированный результат без вызова fetcher; по истечении ttlMs — снова вызвать fetcher. Функция должна возвращать Promise. Кэш реализуй через Map (замыкание или объект модуля).
const cached = cache.get(url); if (cached && Date.now() - cached.timestamp < ttlMs) return cached.data; const data = await fetcher(url); cache.set(url, { data, timestamp: Date.now() }); return data.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке