← Назад к вопросам

Как работает await с непромисами?

1.2 Junior🔥 291 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает await с непромисами?

await работает не только с Promise! Это один из самых неожиданных аспектов async/await в JavaScript. Когда вы используете await с непромисом, JavaScript автоматически преобразует значение.

Базовое поведение

// await ждёт Promise
const result1 = await Promise.resolve(5);
console.log(result1); // 5

// await с обычным значением просто возвращает его
const result2 = await 5;
console.log(result2); // 5 (не Promise, не ошибка, просто 5)

// await с массивом
const result3 = await [1, 2, 3];
console.log(result3); // [1, 2, 3]

// await со строкой
const result4 = await 'hello';
console.log(result4); // "hello"

Это возможно благодаря Protocol Promise — JavaScript преобразует непромисы в Promise.

Как работает преобразование

Когда вы используете await с непромисом, JavaScript:

  1. Проверяет, является ли значение Promise
  2. Если да — ждёт его
  3. Если нет — оборачивает его в Promise.resolve(value)
// Эти два эквивалентны:
const a = await 5;
const b = await Promise.resolve(5);

// Обе дают результат: 5

Более сложный пример

async function example() {
  // await с непромисом
  const num = await 42;
  console.log(num); // 42
  
  // await с объектом
  const obj = await { name: 'John', age: 30 };
  console.log(obj); // { name: 'John', age: 30 }
  
  // await с функцией (возвращает функцию!)
  const func = await (() => console.log('Hi'));
  func(); // "Hi"
  
  // await с null
  const n = await null;
  console.log(n); // null
}

example();

Thenable объекты

Thenable — это объект с методом .then(), похожий на Promise, но не совсем Promise.

const thenable = {
  then(resolve, reject) {
    resolve('Из thenable!');
  }
};

async function test() {
  const result = await thenable; // await работает!
  console.log(result); // "Из thenable!"
}

test();

JavaScript игнорирует, является ли это реальным Promise. Если у объекта есть .then() — он работает с await.

Практический пример с thenable

class CustomAsync {
  constructor(value) {
    this.value = value;
  }
  
  then(resolve) {
    console.log('then() вызван');
    setTimeout(() => resolve(this.value), 1000);
  }
}

async function main() {
  const result = await new CustomAsync('Привет!');
  console.log(result); // "Привет!" (через 1 секунду)
}

main();

await с классом, имеющим then

class MyClass {
  constructor(value) {
    this.value = value;
  }
  
  then(onFulfilled, onRejected) {
    // Это делает класс совместимым с await
    onFulfilled(this.value * 2);
  }
}

async function example() {
  const obj = new MyClass(21);
  const result = await obj; // instanceof MyClass === true, но работает!
  console.log(result); // 42
}

example();

Цепочка await с непромисами

async function chain() {
  const a = await 1;
  console.log(a); // 1
  
  const b = await a + 1; // await 2
  console.log(b); // 2
  
  const c = await Promise.resolve(b + 1);
  console.log(c); // 3
}

chain();

Ошибки и исключения

async function example() {
  // await с непромисом — не может быть отклонено
  const a = await 5; // всегда успешно
  
  // await с отклоненным Promise
  try {
    const b = await Promise.reject('Ошибка!');
  } catch (err) {
    console.log(err); // "Ошибка!"
  }
}

example();

Производительность: синхронное vs асинхронное

// Синхронный вызов быстрее
function sync() {
  return 5;
}

function asyncWrap() {
  return Promise.resolve(5);
}

async function test1() {
  const a = await sync(); // Преобразуется в Promise.resolve(5)
}

async function test2() {
  const b = await asyncWrap(); // Уже Promise
}

// test1 имеет overhead преобразования, но разница минимальна (микросекунды)

Интересный пример: автоматическое преобразование

async function example() {
  // Это не ошибка, это работает!
  const user = await {
    id: 1,
    name: 'John',
    sayHi() { return 'Hi'; }
  };
  
  console.log(user.name); // "John"
  console.log(user.sayHi()); // "Hi"
}

example();

Array как thenable

// Массивы не имеют .then(), поэтому это не работает как thenable
const arr = [1, 2, 3];

async function test() {
  const result = await arr; // Просто вернёт массив
  console.log(result); // [1, 2, 3] (не выполняет элементы)
}

test();

Проблема: забытый return вместо await

async function wrong() {
  return fetchData(); // Ошибка! Возвращает Promise
}

async function correct() {
  return await fetchData(); // Правильно (хотя await здесь не нужен в return)
}

// На самом деле в return await не нужен:
async function bestPractice() {
  return fetchData(); // Просто верни Promise
}

Когда это полезно?

  1. Теоретическое понимание: Показывает, как JavaScript работает под капотом

  2. Кастомные async-like объекты:

class Delayed {
  constructor(value, ms) {
    this.value = value;
    this.ms = ms;
  }
  
  then(resolve) {
    setTimeout(() => resolve(this.value), this.ms);
  }
}

await new Delayed('Success!', 1000); // Работает как Promise!
  1. Обратная совместимость: Старые API, которые возвращают thenable

Спецификация: Promise Resolution

В спецификации ECMAScript это называется Promise Resolution Procedure:

await x →
  1. Если x это Promise → ждём его
  2. Если x это thenable (имеет .then) → используем как Promise
  3. Если x это значение → return Promise.resolve(x)

Итоговая таблица

Что передаём awaitРезультат
Promise<T>T (ждём результат)
ThenableРезультат .then()
Число/строка/nullСамо значение
ОбъектСамо значение
ФункцияСама функция
undefinedundefined

Практический совет

// ❌ Плохо: ненужный await на непромисе
const value = await 5;

// ✅ Хорошо: просто используй значение
const value = 5;

// ✅ Правильно: await только на Promises
const data = await fetchData();

Понимание того, как await работает с непромисами, помогает глубже понять асинхронность в JavaScript и написать более предсказуемый код.