Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Как повторить неудачную операцию

Короткий ответ

Вызывайте функцию в цикле. Если она выполнилась — верните результат, если нет — попробуйте снова, а после последней попытки передайте ошибку дальше:

async function retry(task, attempts = 3) {
  for (let i = 1; i <= attempts; i++) {
    try {
      return await task();
    } catch (error) {
      console.log(`attempt ${i} failed: ${error.message}`);
      if (i === attempts) throw error;
    }
  }
}
 
let calls = 0;
const unstable = async () => {
  calls++;
  if (calls < 3) throw new Error('server busy');
  return 'data';
};
 
console.log(await retry(unstable));

Результат:

attempt 1 failed: server busy
attempt 2 failed: server busy
data

Растущая пауза

Не повторяйте сразу: после каждой неудачи ждите дольше. Это называется экспоненциальной задержкой, она даёт серверу время прийти в себя:

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
 
async function retry(task, attempts = 4, delay = 100) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await task();
    } catch (error) {
      if (i === attempts - 1) throw error;
      const wait = delay * 2 ** i;
      console.log(`waiting ${wait} ms`);
      await sleep(wait);
    }
  }
}
 
let calls = 0;
const result = await retry(async () => {
  if (++calls < 4) throw new Error('timeout');
  return 'ok';
});
 
console.log(result);

Результат:

waiting 100 ms
waiting 200 ms
waiting 400 ms
ok

С fetch

Повторяйте только временные сбои: ошибки сети и статусы от 500. Ошибка 404 после паузы не исчезнет:

const response = await retry(async () => {
  const res = await fetch('/api/orders');
  if (res.status >= 500) throw new Error(`Server error ${res.status}`);
  return res;
});

Частая ошибка

Без await в return await task() ошибка промиса не перехватывается внутри try, и повтора не происходит:

async function retryBroken(task) {
  for (let i = 1; i <= 3; i++) {
    try {
      return task();
    } catch {
      console.log('retrying');
    }
  }
}
 
try {
  await retryBroken(async () => {
    throw new Error('failed');
  });
} catch (error) {
  console.log('no retries:', error.message);
}

Результат:

no retries: failed

Функции из этого ответа

  • Promise — Обещание результата, который будет готов позже: успех или ошибка.
  • async function — Функция, которая всегда возвращает промис и внутри которой можно использовать await.
  • try...catch — Перехватывает ошибки, чтобы программа не остановилась.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.