Как повторить попытку разрешения Promise N раз с задержкой между попытками? - PullRequest
2 голосов
/ 10 апреля 2019

Я хочу, чтобы JavaScript-код принимал 3 параметра в качестве параметров:

  • Функция, возвращающая Обещание.
  • Максимальное количество попыток.
  • Задержка между каждой попыткой.

Я закончил тем, что использовал цикл for. Я не хотел использовать рекурсивную функцию: таким образом, даже если есть 50 попыток, стек вызовов не на 50 строк длиннее.

Вот машинопись версия кода:

/**
 * @async
 * @function tryNTimes<T> Tries to resolve a {@link Promise<T>} N times, with a delay between each attempt.
 * @param {Object} options Options for the attempts.
 * @param {() => Promise<T>} options.toTry The {@link Promise<T>} to try to resolve.
 * @param {number} [options.times=5] The maximum number of attempts (must be greater than 0).
 * @param {number} [options.interval=1] The interval of time between each attempt in seconds.
 * @returns {Promise<T>} The resolution of the {@link Promise<T>}.
 */
export async function tryNTimes<T>(
    {
        toTry,
        times = 5,
        interval = 1,
    }:
        {
            toTry: () => Promise<T>,
            times?: number,
            interval?: number,
        }
): Promise<T> {
    if (times < 1) throw new Error(`Bad argument: 'times' must be greater than 0, but ${times} was received.`);
    let attemptCount: number;
    for (attemptCount = 1; attemptCount <= times; attemptCount++) {
        let error: boolean = false;
        const result = await toTry().catch((reason) => {
            error = true;
            return reason;
        });

        if (error) {
            if (attemptCount < times) await delay(interval);
            else return Promise.reject(result);
        }
        else return result;
    }
}

Используемая выше функция delay является многообещающим тайм-аутом:

/**
 * @function delay Delays the execution of an action.
 * @param {number} time The time to wait in seconds.
 * @returns {Promise<void>}
 */
export function delay(time: number): Promise<void> {
    return new Promise<void>((resolve) => setTimeout(resolve, time * 1000));
}

Чтобы уточнить: код выше работает , мне только интересно, если это "хороший" способ сделать это, и если нет, как я мог бы улучшить его.

Есть предложения? Заранее спасибо за помощь.

Ответы [ 4 ]

1 голос
/ 12 апреля 2019

Использование рекурсивных функций с Promises не будет проблемой для callstack, поскольку Promise возвращается мгновенно, а функция then или catch будет вызываться после асинхронного события.

Простой javascriptфункция будет выглядеть так:

function wait (ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms)
  })
}

function retry (fn, maxAttempts = 1, delay = 0, attempts = 0) {
  return Promise.resolve()
    .then(fn)
    .catch(err => {
      if (attempts < maxAttempts) {
        return retry (fn, maxAttempts, delay, attempts + 1)
      }
      throw err
    })
}
1 голос
/ 10 апреля 2019

Я не хотел использовать рекурсивную функцию: таким образом, даже если есть 50 попыток, стек вызовов не на 50 строк длиннее.

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

В итоге я использовал forпетля.Это «хороший» способ сделать это, и если нет, то как я мог бы улучшить это?

Цикл for - это хорошо.Немного странно, что он начинается с 1, хотя циклы на основе 0 гораздо более идиоматичны.

Однако, что не очень хорошо, так это ваша странная обработка ошибок.Этот логический флаг error не должен иметь места в вашем коде. Использование .catch() нормально , но try / catch будет работать так же хорошо и должно быть предпочтительным.

export async function tryNTimes<T>({ toTry, times = 5, interval = 1}) {
    if (times < 1) throw new Error(`Bad argument: 'times' must be greater than 0, but ${times} was received.`);
    let attemptCount = 0
    while (true) {
        try {
            const result = await toTry();
            return result;
        } catch(error) {
            if (++attemptCount >= times) throw error;
        }
        await delay(interval)
    }
}
1 голос
/ 10 апреля 2019

Возможно, вы захотите взглянуть на async-retry , которая делает именно то, что вам нужно. Этот пакет позволяет вам повторять асинхронные операции, и вы можете настроить (среди прочего) тайм-ауты между попытками (даже с увеличивающимися коэффициентами), максимальное количество повторов,…

Таким образом, вам не нужно изобретать велосипед, но вы можете положиться на проверенный пакет, который широко используется в сообществе.

0 голосов
/ 10 апреля 2019

Рассматривали ли вы RxJS?

Это отлично подходит для реализации логики такого рода в асинхронных рабочих процессах.

Ниже приведен пример того, как вы будете делать это безвзлом вашего публичного API (т.е. преобразование из Promise в Observable и обратно).На практике вы, возможно, захотите использовать RxJS или Promises в любом конкретном проекте, а не смешивать их.

/**
 * @async
 * @function tryNTimes<T> Tries to resolve a {@link Promise<T>} N times, with a delay between each attempt.
 * @param {Object} options Options for the attempts.
 * @param {() => Promise<T>} options.toTry The {@link Promise<T>} to try to resolve.
 * @param {number} [options.times=5] The maximum number of attempts (must be greater than 0).
 * @param {number} [options.interval=1] The interval of time between each attempt in seconds.
 * @returns {Promise<T>} The resolution of the {@link Promise<T>}.
 */
export async function tryNTimes<T>(
    {
        toTry,
        times = 5,
        interval = 1,
    }:
        {
            toTry: () => Promise<T>,
            times?: number,
            interval?: number,
        }
): Promise<T> {
    if (times < 1) throw new Error(`Bad argument: 'times' must be greater than 0, but ${times} was received.`);
    let attemptCount: number;

    return from(toTry)
        .pipe(
            retryWhen(errors =>
                errors.pipe(
                    delay(interval * 1000),
                    take(times - 1)
                )
            )
        )
        .toPromise();
}

Возможно, не стоит добавлять целую библиотеку для этого одного фрагменталогика, но если ваш проект включает в себя множество сложных асинхронных рабочих процессов, таких как этот, тогда RxJS великолепен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...