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

Как выполнить операцию вне зависимости от итога выполнения Promise?

2.3 Middle🔥 241 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Promise.finally(): выполнение кода независимо от итога

Для выполнения операции независимо от того, успешно ли выполнился Promise (resolve или reject), используется метод .finally(). Это гарантирует, что код в блоке finally выполнится в любом случае.

Синтаксис

promise
  .then(result => console.log('Success:', result))
  .catch(error => console.error('Error:', error))
  .finally(() => console.log('Done, regardless of outcome'));

Основной пример

const fetchUser = (id) => {
  return fetch(`/api/users/${id}`)
    .then(response => response.json())
    .then(data => {
      console.log('User:', data);
      return data;
    })
    .catch(error => {
      console.error('Failed to fetch:', error);
      throw error;
    })
    .finally(() => {
      console.log('Fetch operation completed');
      // Здесь выполнится ВСЕГДА — и при успехе, и при ошибке
    });
};

fetchUser(1)
  .then(user => console.log('Got user:', user))
  .catch(err => console.log('Handled error:', err));

Вывод при успехе:

User: {id: 1, name: 'John'}
Fetch operation completed
Got user: {id: 1, name: 'John'}

Вывод при ошибке:

Failed to fetch: Error: Network error
Fetch operation completed
Handled error: Error: Network error

Типичные варианты использования

1. Остановка loading индикатора

export function useApi<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = () => {
    setLoading(true);
    setError(null);

    fetch(url)
      .then(r => r.json())
      .then(data => setData(data))
      .catch(err => setError(err))
      .finally(() => setLoading(false)); // Всегда останавливает loading
  };

  return { data, loading, error, fetchData };
}

2. Очистка ресурсов

let connection;

const openConnection = (url) => {
  return new Promise((resolve, reject) => {
    connection = new WebSocket(url);
    connection.onopen = () => resolve('Connected');
    connection.onerror = () => reject(new Error('Connection failed'));
  })
    .finally(() => {
      // Очистка ресурсов — выполнится независимо от результата
      console.log('Connection process finished, cleanup resources');
    });
};

3. Скрытие модального диалога

const submitForm = (data) => {
  setSubmitting(true);

  return api.post('/form', data)
    .then(response => {
      showSuccess('Form submitted!');
      return response.data;
    })
    .catch(error => {
      showError(error.message);
      throw error;
    })
    .finally(() => {
      // Закрыть модальное окно в любом случае
      closeModal();
      setSubmitting(false);
    });
};

4. Логирование

const executeTask = (task) => {
  const startTime = Date.now();

  return performTask(task)
    .finally(() => {
      const duration = Date.now() - startTime;
      console.log(`Task "${task.name}" took ${duration}ms`);
      // Логирование выполнится всегда
    });
};

async/await с finally

// С finally блоком
async function fetchAndProcess() {
  let data;
  try {
    const response = await fetch('/api/data');
    data = await response.json();
    console.log('Data:', data);
  } catch (error) {
    console.error('Error:', error);
  } finally {
    console.log('Operation complete');
  }
  return data;
}

Это эквивалентно:

function fetchAndProcess() {
  return fetch('/api/data')
    .then(response => response.json())
    .then(data => {
      console.log('Data:', data);
      return data;
    })
    .catch(error => {
      console.error('Error:', error);
    })
    .finally(() => {
      console.log('Operation complete');
    });
}

Важные детали

1. finally НЕ изменяет результат Promise

Promise.resolve(5)
  .finally(() => 'Ignored value')
  .then(value => console.log(value)); // 5, не 'Ignored value'

Promise.reject(new Error('Oops'))
  .finally(() => 'Ignored value')
  .catch(error => console.log(error)); // Error: Oops

2. Если finally бросит ошибку, она подавит предыдущую

Promise.reject(new Error('First error'))
  .finally(() => {
    throw new Error('Second error');
  })
  .catch(error => {
    console.log(error.message); // 'Second error' — первая ошибка подавлена
  });

3. Порядок выполнения

console.log('Start');

Promise.resolve('value')
  .then(v => { console.log('Then:', v); return v; })
  .finally(() => { console.log('Finally'); })
  .then(() => { console.log('Then after finally'); });

console.log('End');

Вывод:

Start
End
Then: value
Finally
Then after finally

React компонент с finally

export function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let mounted = true;

    setLoading(true);
    setError(null);

    fetch(`/api/users/${userId}`)
      .then(r => {
        if (!r.ok) throw new Error('Failed to fetch');
        return r.json();
      })
      .then(data => {
        if (mounted) setUser(data);
      })
      .catch(err => {
        if (mounted) setError(err);
      })
      .finally(() => {
        if (mounted) setLoading(false);
      });

    return () => {
      mounted = false; // Cleanup при unmount
    };
  }, [userId]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return null;

  return <div>{user.name}</div>;
}

Сравнение подходов

// ❌ Без finally — нужно дублировать cleanup
.then(data => {
  setLoading(false);
  return data;
})
.catch(error => {
  setLoading(false);
  throw error;
})

// ✅ С finally — cleanup один раз
.finally(() => {
  setLoading(false);
})

finally для отладки

const debugPromise = (promise, name) => {
  return promise
    .then(result => {
      console.log(`[${name}] Resolved:`, result);
      return result;
    })
    .catch(error => {
      console.error(`[${name}] Rejected:`, error);
      throw error;
    })
    .finally(() => {
      console.log(`[${name}] Finished`);
    });
};

// Использование
debugPromise(fetch('/api/data'), 'API Call')
  .then(r => r.json())
  .then(data => console.log('Success:', data))
  .catch(err => console.log('Handled:', err));

Итог

.finally() используется для:

  • Остановки loading индикаторов
  • Закрытия модальных окон
  • Очистки ресурсов (соединения, таймеры)
  • Логирования операций
  • Убирания дублирования cleanup кода

Это essential паттерн для надёжной обработки асинхронного кода в JavaScript.