Шаблоны Promise Retry Design
редактировать
- Шаблон, который продолжает повторяться до разрешения обещания (с задержкой и maxRetries).
- Шаблон, который продолжает повторяться до тех пор, пока условие не будет соответствовать результату (с задержкой и maxRetries).
- Эффективный по памяти динамический шаблон с неограниченным количеством повторов (задержка при условии).
Код для № 1. Продолжает повторять попытки до тех пор, пока обещание не разрешится (какие-либо улучшения сообщества для языка и т.д.?)
Promise.retry = function(fn, times, delay) {
return new Promise(function(resolve, reject){
var error;
var attempt = function() {
if (times == 0) {
reject(error);
} else {
fn().then(resolve)
.catch(function(e){
times--;
error = e;
setTimeout(function(){attempt()}, delay);
});
}
};
attempt();
});
};
использование
work.getStatus()
.then(function(result){ //retry, some glitch in the system
return Promise.retry(work.unpublish.bind(work, result), 10, 2000);
})
.then(function(){console.log('done')})
.catch(console.error);
Код № 2 повторять до тех пор, пока не будет выполнено условие then
результат в многократном использовании (условие, что будет меняться).
work.publish()
.then(function(result){
return new Promise(function(resolve, reject){
var intervalId = setInterval(function(){
work.requestStatus(result).then(function(result2){
switch(result2.status) {
case "progress": break; //do nothing
case "success": clearInterval(intervalId); resolve(result2); break;
case "failure": clearInterval(intervalId); reject(result2); break;
}
}).catch(function(error){clearInterval(intervalId); reject(error)});
}, 1000);
});
})
.then(function(){console.log('done')})
.catch(console.error);
23 ответа
Что-то немного другое...
Асинхронные повторы могут быть достигнуты путем создания .catch()
цепь, в отличие от более обычной .then()
цепь.
Этот подход:
- возможно только с указанным максимальным количеством попыток. (Цепь должна быть конечной длины),
- рекомендуется только с низким максимумом. (Цепочки обещаний потребляют память, примерно пропорциональную их длине).
В противном случае используйте рекурсивное решение.
Во-первых, функция полезности, которая будет использоваться в качестве .catch()
Перезвоните.
var t = 500;
function rejectDelay(reason) {
return new Promise(function(resolve, reject) {
setTimeout(reject.bind(null, reason), t);
});
}
Теперь вы можете создавать цепочки.catch очень кратко:
1. Повторите попытку, пока обещание не будет выполнено, с задержкой.
var max = 5;
var p = Promise.reject();
for(var i=0; i<max; i++) {
p = p.catch(attempt).catch(rejectDelay);
}
p = p.then(processResult).catch(errorHandler);
ДЕМО: https://jsfiddle.net/duL0qjqe/
2. Повторите попытку, пока результат не встретит некоторое условие, без задержки
var max = 5;
var p = Promise.reject();
for(var i=0; i<max; i++) {
p = p.catch(attempt).then(test);
}
p = p.then(processResult).catch(errorHandler);
ДЕМО: https://jsfiddle.net/duL0qjqe/1/
3. Повторите попытку, пока результат не встретит некоторое условие, с задержкой
Обдумав (1) и (2), комбинированный тест + задержка одинаково тривиален.
var max = 5;
var p = Promise.reject();
for(var i=0; i<max; i++) {
p = p.catch(attempt).then(test).catch(rejectDelay);
// Don't be tempted to simplify this to `p.catch(attempt).then(test, rejectDelay)`. Test failures would not be caught.
}
p = p.then(processResult).catch(errorHandler);
test()
может быть синхронным или асинхронным.
Также было бы тривиально добавить дополнительные тесты. Просто вставьте цепочку между двумя защелками.
p = p.catch(attempt).then(test1).then(test2).then(test3).catch(rejectDelay);
ДЕМО: https://jsfiddle.net/duL0qjqe/3/
Все версии предназначены для attempt
быть возвращающей обещание асинхронной функцией. Он также может предположительно вернуть значение, и в этом случае цепочка будет следовать по пути успеха к следующему / терминалу. .then()
,
2. Шаблон, который продолжает повторяться до тех пор, пока условие не будет соответствовать результату (с задержкой и maxRetries).
Это хороший способ сделать это с родными обещаниями рекурсивным способом:
const wait = ms => new Promise(r => setTimeout(r, ms));
const retryOperation = (operation, delay, times) => new Promise((resolve, reject) => {
return operation()
.then(resolve)
.catch((reason) => {
if (times - 1 > 0) {
return wait(delay)
.then(retryOperation.bind(null, operation, delay, times - 1))
.then(resolve)
.catch(reject);
}
return reject(reason);
});
});
Вот как вы это называете, предполагая, что func
иногда успешно, а иногда и не всегда, возвращая строку, которую мы можем записать:
retryOperation(func, 1000, 5)
.then(console.log)
.catch(console.log);
Здесь мы вызываем retryOperation и просим его повторять каждую секунду, а max retries = 5.
Если вы хотите что-то более простое без обещаний, RxJs помогут с этим: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/retrywhen.md
Упоминается много хороших решений, и теперь с помощью async/await эти проблемы могут быть решены без особых усилий.
Если вы не возражаете против рекурсивного подхода, то это мое решение.
function retry(fn, retries=3, err=null) {
if (!retries) {
return Promise.reject(err);
}
return fn().catch(err => {
return retry(fn, (retries - 1), err);
});
}
Вы можете связать новое обещание с предыдущим, откладывая его окончательное решение до тех пор, пока не узнаете окончательный ответ. Если следующий ответ по-прежнему неизвестен, тогда добавьте к нему еще одно обещание и продолжайте цепочку checkStatus() до тех пор, пока в конце концов вы не узнаете ответ и не сможете вернуть окончательное решение. Это может работать так:
function delay(t) {
return new Promise(function(resolve) {
setTimeout(resolve, t);
});
}
function checkStatus() {
return work.requestStatus().then(function(result) {
switch(result.status) {
case "success":
return result; // resolve
case "failure":
throw result; // reject
case default:
case "inProgress": //check every second
return delay(1000).then(checkStatus);
}
});
}
work.create()
.then(work.publish) //remote work submission
.then(checkStatus)
.then(function(){console.log("work published"})
.catch(console.error);
Обратите внимание, я также избегал создавать обещания вокруг вашего switch
заявление. Так как вы уже в .then()
обработчик, просто возвращение значения является решением, создание исключения - отклонением, а возвращение обещания связывает новое обещание с предыдущим. Это охватывает три ветви вашего switch
заявление без создания нового обещания там. Для удобства я использую delay()
функция, которая основана на обещании.
К вашему сведению, это предполагает work.requestStatus()
не нужно никаких аргументов. Если для этого нужны конкретные аргументы, вы можете передать их в точке вызова функции.
Также может быть хорошей идеей реализовать какое-то значение тайм-аута для того, как долго вы будете зацикливаться в ожидании завершения, чтобы это никогда не продолжалось вечно. Вы можете добавить функцию тайм-аута следующим образом:
function delay(t) {
return new Promise(function(resolve) {
setTimeout(resolve, t);
});
}
function checkStatus(timeout) {
var start = Date.now();
function check() {
var now = Date.now();
if (now - start > timeout) {
return Promise.reject(new Error("checkStatus() timeout"));
}
return work.requestStatus().then(function(result) {
switch(result.status) {
case "success":
return result; // resolve
case "failure":
throw result; // reject
case default:
case "inProgress": //check every second
return delay(1000).then(check);
}
});
}
return check;
}
work.create()
.then(work.publish) //remote work submission
.then(checkStatus(120 * 1000))
.then(function(){console.log("work published"})
.catch(console.error);
Я не уверен, какой именно "шаблон дизайна" вы ищете. Так как вы, кажется, возражаете против внешне заявленного checkStatus()
функция, вот встроенная версия:
work.create()
.then(work.publish) //remote work submission
.then(work.requestStatus)
.then(function() {
// retry until done
var timeout = 10 * 1000;
var start = Date.now();
function check() {
var now = Date.now();
if (now - start > timeout) {
return Promise.reject(new Error("checkStatus() timeout"));
}
return work.requestStatus().then(function(result) {
switch(result.status) {
case "success":
return result; // resolve
case "failure":
throw result; // reject
case default:
case "inProgress": //check every second
return delay(1000).then(check);
}
});
}
return check();
}).then(function(){console.log("work published"})
.catch(console.error);
Более повторяющаяся схема повторных попыток, которая может использоваться во многих обстоятельствах, будет определять некоторый внешний код многократного использования, но вы, похоже, возражаете против этого, поэтому я не сделал эту версию.
Вот еще один подход, который использует .retryUntil()
метод на Promise.prototype
по вашему запросу. Если вы хотите настроить детали реализации этого, вы сможете изменить этот общий подход:
// fn returns a promise that must be fulfilled with an object
// with a .status property that is "success" if done. Any
// other value for that status means to continue retrying
// Rejecting the returned promise means to abort processing
// and propagate the rejection
// delay is the number of ms to delay before trying again
// no delay before the first call to the callback
// tries is the max number of times to call the callback before rejecting
Promise.prototype.retryUntil = function(fn, delay, tries) {
var numTries = 0;
function check() {
if (numTries >= tries) {
throw new Error("retryUntil exceeded max tries");
}
++numTries;
return fn().then(function(result) {
if (result.status === "success") {
return result; // resolve
} else {
return Promise.delay(delay).then(check);
}
});
}
return this.then(check);
}
if (!Promise.delay) {
Promise.delay = function(t) {
return new Promise(function(resolve) {
setTimeout(resolve, t);
});
}
}
work.create()
.then(work.publish) //remote work submission
.retryUntil(function() {
return work.requestStatus().then(function(result) {
// make this promise reject for failure
if (result.status === "failure") {
throw result;
}
return result;
})
}, 2000, 10).then(function() {
console.log("work published");
}).catch(console.error);
Я до сих пор не могу сказать, что вы хотите, или что все эти подходы не решают вашу проблему. Поскольку все ваши подходы, по-видимому, представляют собой весь встроенный код и не используют многократный помощник, вот один из них:
work.create()
.then(work.publish) //remote work submission
.then(function() {
var tries = 0, maxTries = 20;
function next() {
if (tries > maxTries) {
throw new Error("Too many retries in work.requestStatus");
}
++tries;
return work.requestStatus().then(function(result) {
switch(result.status) {
case "success":
return result;
case "failure":
// if it failed, make this promise reject
throw result;
default:
// for anything else, try again after short delay
// chain to the previous promise
return Promise.delay(2000).then(next);
}
});
}
return next();
}).then(function(){
console.log("work published")
}).catch(console.error);
Вот экспоненциальная реализация повторных попыток отката с использованием async/await, которая может обернуть любой обещающий API. Он симулирует нестабильную конечную точку с математической случайностью, поэтому попробуйте несколько раз, чтобы увидеть как успехи, так и неудачи.
/**
* Wrap a promise API with a function that will attempt the promise over and over again
* with exponential backoff until it resolves or reaches the maximum number of retries.
* - First retry: 500 ms + <random> ms
* - Second retry: 1000 ms + <random> ms
* - Third retry: 2000 ms + <random> ms
* and so forth until maximum retries are met, or the promise resolves.
*/
const withRetries = ({ attempt, maxRetries }) => async (...args) => {
const slotTime = 500;
let retryCount = 0;
do {
try {
console.log('Attempting...', Date.now());
return await attempt(...args);
} catch (error) {
const isLastAttempt = retryCount === maxRetries;
if (isLastAttempt) {
// Stack Overflow console doesn't show unhandled
// promise rejections so lets log the error.
console.error(error);
return Promise.reject(error);
}
}
const randomTime = Math.floor(Math.random() * slotTime);
const delay = 2 ** retryCount * slotTime + randomTime;
// Wait for the exponentially increasing delay period before retrying again.
await new Promise(resolve => setTimeout(resolve, delay));
} while (retryCount++ < maxRetries);
}
const fakeAPI = (arg1, arg2) => Math.random() < 0.25 ? Promise.resolve(arg1) : Promise.reject(new Error(arg2))
const fakeAPIWithRetries = withRetries({ attempt: fakeAPI, maxRetries: 3 });
fakeAPIWithRetries('arg1', 'arg2').then(results => console.log(results))
Проверьте @ jsier / retrier. Протестировано, задокументировано, легковесно, просто в использовании, без внешних зависимостей и уже довольно давно в производстве.
Поддерживает:
- Задержка первой попытки
- Задержка между попытками
- Ограничение количества попыток
- Обратный вызов для остановки повторных попыток, если выполняется какое-либо условие (например, обнаружена конкретная ошибка)
- Обратный вызов для продолжения повторной попытки, если выполняется какое-то условие (например, разрешенное значение неудовлетворительно)
Установка:
npm install @jsier/retrier --save
Применение:
import { Retrier } from '@jsier/retrier';
const options = { limit: 5, delay: 2000 };
const retrier = new Retrier(options);
retrier
.resolve(attempt => new Promise((resolve, reject) => reject('Dummy reject!')))
.then(
result => console.log(result),
error => console.error(error) // After 5 attempts logs: "Dummy reject!"
);
Пакет не имеет внешних зависимостей.
Вот моя попытка. Я попытался извлечь из всех вышеперечисленных ответов то, что мне понравилось. Никаких внешних зависимостей. Машинопись + async / await (ES2017)
export async function retryOperation<T>(
operation: () => (Promise<T> | T), delay: number, times: number): Promise<T> {
try {
return await operation();
} catch (ex) {
if (times > 1) {
await new Promise((resolve) => setTimeout(resolve, delay));
return retryOperation(operation, delay, times - 1);
} else {
throw ex;
}
}
}
Применение:
function doSomething() {
return Promise.resolve('I did something!');
}
const retryDelay = 1000; // 1 second
const retryAttempts = 10;
retryOperation(doSomething(), retryDelay, retryAttempts)
.then((something) => console.log('I DID SOMETHING'))
.catch((err) => console.error(err));
Основываясь на решении Холмберда с немного более чистым кодом и задержкой
const wait = seconds => new Promise((resolve) => {
setTimeout(() => resolve(), seconds * 1000)
})
const retryWithDelay = async (
fn, retries = 3, interval = 50,
finalErr = Error('Retry failed')
) => fn().catch(async () => {
if (retries <= 0) {
return Promise.reject(finalErr);
}
await wait(interval)
return retryWithDelay(fn, (retries - 1), interval, finalErr);
}
}
Если ваш код помещен в класс, вы можете использовать для этого декоратор. Такой декоратор у вас есть в утилитах-декораторах (npm install --save utils-decorators
):
import {retry} from 'utils-decorators';
class SomeService {
@retry(3)
doSomeAsync(): Promise<any> {
....
}
}
Примечание: эта библиотека может быть изменена по дереву, поэтому вы не будете платить лишние байты за остальные доступные декораторы в этой библиотеке.
Не уверен, почему все предлагаемые решения рекурсивны. Итеративное решение с TypeScript, которое ждет, пока метод не вернет что-то, что не является неопределенным:
function DelayPromise(delayTime): Promise<void> {
return new Promise<void>((resolve) => setTimeout(resolve, delayTime));
}
interface RetryOptions {
attempts?: number;
delayMs?: number;
}
export async function retryOperation<T>(
operation: (attempt: number) => Promise<T>,
options: RetryOptions = {}
): Promise<T> {
const { attempts = 6, delayMs = 10000 } = options;
for (let i = 0; i < attempts; i++) {
const result = await operation(i);
if (typeof result !== 'undefined') {
return result;
}
await DelayPromise(delayMs);
}
throw new Error('Timeout');
}
Здесь есть много ответов, но после некоторых исследований я решил пойти с рекурсивным подходом. Я оставляю свое решение здесь для всех, кого интересует
function retry(fn, retriesLeft = 2, interval = 1000) {
return new Promise((resolve, reject) => {
fn()
.then(resolve)
.catch((error) => {
if (retriesLeft === 0) {
reject(error);
return;
}
setTimeout(() => {
console.log('retrying...')
retry(fn, retriesLeft - 1, interval).then(resolve).catch(reject);
}, interval);
});
});
}
Вот stackblitz с красивой игровой площадкой, где вы можете почувствовать, как это работает. Просто поиграйте с переменной намерения, чтобы увидеть разрешение / отклонение обещания
Вот мое решение:
- Сохраняйте тип функции с помощью Typescript.
- Принять функцию с любыми параметрами.
- Настроить количество
maxRetries
. - Настройка поведения задержки
type AnyFn = (...any: any[]) => any;
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
type DelayFn = (retry: number) => number;
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export function retry<Fn extends AnyFn>(
fn: Fn,
maxRetries: number,
getDelay: DelayFn = () => 5000
) {
let retries = 0;
return async function wrapped(
...args: Parameters<Fn>
): Promise<Awaited<ReturnType<Fn>>> {
try {
return await fn(...args);
} catch (e) {
if (++retries > maxRetries) throw e;
const delayTime = getDelay(retries);
console.error(e);
console.log(`Retry ${fn.name} ${retries} times after delaying ${delayTime}ms`);
await delay(delayTime);
return await wrapped(...args);
}
};
}
Применение
const badFn = () => new Promise((resolve, reject) => reject('Something is wrong');
const fn = retry(badFn, 5, (retry) => 2 ** retry * 1000);
fn();
// Something is wrong
// Retry badFn 1 times after delaying 2000ms
// Something is wrong
// Retry badFn 2 times after delaying 4000ms
// Something is wrong
// Retry badFn 3 times after delaying 8000ms
// Something is wrong
// Retry badFn 4 times after delaying 16000ms
// Something is wrong
// Retry badFn 5 times after delaying 32000ms
async-retry.ts пытается реализовать шаблон, я использую его в производстве для некоторых проектов.
Монтаж:
npm установить async-retry.ts --save
Использование:
import Action from 'async-retry.ts'
const action = async()=>{}
const handlers = [{
error: 'error1',
handler: async yourHandler1()=>{}
}, {
error: 'error2',
handler: async yourHandler2()=>{}
}]
await Action.retryAsync(action, 3, handlers)
Этот пакет довольно новый, но он получен из долгоживущего пакета co-retry
который реализовал retry pattern
в моде функции генератора.
function TryToSuccess(fun, reties) {
let attempt = 0;
let doTry = (...args) => {
attempt++;
return fun(...args)
.catch((err) => {
console.log("fail ", attempt);
if(attempt <= reties){
return doTry(...args);
} else {
return Promise.reject(err);
}
});
}
return doTry;
}
function asyncFunction(){
return new Promise((resolve, reject) => {
setTimeout(() => {
(window.findResult === true) ? resolve("Done") : reject("fail");
}, 2000);
});
}
var cloneFunc = TryToSuccess(asyncFunction, 3);
cloneFunc()
.then(res => {
console.log("Got Success. ", res)
})
.catch(err => {
console.log("Rejected with err ", err);
});
setTimeout(() => {
window.findResult = true;
}, 4000);
Это отлично работает для меня:
async wait(timeInMilliseconds: number, name?: string) {
const messageSuffix = name ? ` => ${name}` : ""
await this.logger.info(`Waiting for ${timeInMilliseconds} ms${messageSuffix}`).then(log => log())
return new Promise<void>(resolve => setTimeout(resolve, timeInMilliseconds))
}
async waitUntilCondition(name: string, condition: () => boolean | Promise<boolean>, scanTimeInSeconds: number, timeoutInSeconds: number) {
await this.logger.info(`Waiting until condition: name=${name}, scanTime: ${scanTimeInSeconds} s, timeout: ${timeoutInSeconds} s`).then(log => log())
const timeoutInMillis = timeoutInSeconds * 1000
return new Promise<void>(async (resolve, reject) => {
const startTime = new Date().getTime()
let completed = false
let iteration = 0
while (!completed) {
if (iteration++ > 0) {
const timingOutInSeconds = Math.round((timeoutInMillis - (new Date().getTime() - startTime)) / 1000.0)
await this.wait(scanTimeInSeconds * 1000, `${name}, timing out in ${timingOutInSeconds} s`)
}
try {
completed = await condition()
if (completed) {
resolve()
return
}
} catch (error: any) {
reject(error)
throw error
}
const waitTimeMillis = new Date().getTime() - startTime
if (waitTimeMillis > timeoutInMillis) {
reject(`The condition '${name}' timed out. Time waited: ${waitTimeMillis / 1000} seconds`)
return
}
}
})
}
work.create()
.then(work.publish) //remote work submission
.then(function(result){
var maxAttempts = 10;
var handleResult = function(result){
if(result.status === 'success'){
return result;
}
else if(maxAttempts <= 0 || result.status === 'failure') {
return Promise.reject(result);
}
else {
maxAttempts -= 1;
return (new Promise( function(resolve) {
setTimeout( function() {
resolve(_result);
}, 1000);
})).then(function(){
return work.requestStatus().then(handleResult);
});
}
};
return work.requestStatus().then(handleResult);
})
.then(function(){console.log("work published"})
.catch(console.error);
Я даю вам решение async/await, получайте удовольствие:)
async function scope() {
/* Performs an operation repeatedly at a given frequency until
it succeeds or a timeout is reached and returns its results. */
async function tryUntil(op, freq, tout) {
let timeLeft = tout;
while (timeLeft > 0) {
try {
return op();
} catch (e) {
console.log(timeLeft + " milliseconds left");
timeLeft -= freq;
}
await new Promise((resolve) => setTimeout(() => resolve(), freq));
}
throw new Error("failed to perform operation");
}
function triesToGiveBig() {
const num = Math.random();
if (num > 0.95) return num;
throw new Error();
}
try {
console.log(await tryUntil(triesToGiveBig, 100, 1000));
} catch (e) {
console.log("too small :(");
}
}
scope();
Мое решение для TypeScript:
export const wait = (milliseconds: number): Promise<void> =>
new Promise(resolve => {
setTimeout(() => resolve(), milliseconds);
});
export const retryWithDelay = async (
fn,
retries = 3,
interval = 300
): Promise<void> =>
fn().catch(async error => {
if (retries <= 0) {
return Promise.reject(error);
}
await wait(interval);
return retryWithDelay(fn, retries - 1, interval);
});
На основе решений, приведенных выше, фиксированные миллисекунды для ожидания, поскольку по умолчанию оно равно 50 секундам, а не мс, и теперь выдает ошибку, вызвавшую сбой, вместо жестко запрограммированного сообщения.
Подход с контролем того, действительно ли операция может быть повторена.
На практике я обнаружил, что не следует предполагать, что операцию всегда можно повторить, и общийretry
помощнику нужен способ делегировать эту проверку вызывающему коду более высокого уровня. Пример ниже показывает, что сработало для меня.
/* The retry function takes a function to invoke, and a set
* of optional parameters to control the delay between retries
* (no backoff algorithm implemented here, but other example
* show how you might add that one), how many times to attempt
* retrying and also a way to check if a retry should be
* attempted.
*
* And it returns a Promise that can be used in promise-
* chaining and other async patterns.
*
*/
const retry = (fn,
ms = 1000,
maxRetries = 2,
fnRetryable) => new Promise((resolve, reject) => {
var retries = 0;
if(!fnRetryable) {
// default to always retryable
fnRetryable = function() { return true };
}
fn()
.then(resolve)
.catch((err) => {
if(!fnRetryable(err)) {
return reject('Non-retryable');
} else {
setTimeout(() => {
++retries;
if(retries == maxRetries) {
return reject('Max retries exceeded');
}
retry(fn, ms).then(resolve);
}, ms);
}
})
});
function doFoo(opts) {
// Return a Promise that resolves after doing something with opts
// or rejects with err.statusCode
}
function doFooWithRetry(opts, ms = 1000, maxRetries = 2) {
var attempt = function() {
return doFoo(opts);
}
var retryable = function(err) {
// example, retry on rate limit error
if(err && err.statusCode == 429) {
return true;
} else {
return false;
}
}
return retry(attempt, ms, maxRetries, retryable);
}
На всякий случай кто-то ищет более общее решение. Вот мои два цента:
Вспомогательная функция:
/**
* Allows to repeatedly call
* an async code block
*
* @callback callback
* @callback [filterError] Allows to differentiate beween different type of errors
* @param {number} [maxRetries=Infinity]
*/
function asyncRetry(
callback,
{ filterError = (error) => true, maxRetries = Infinity } = {}
) {
// Initialize a new counter:
let tryCount = 0;
// Next return an async IIFY that is able to
// call itself recursively:
return (async function retry() {
// Increment out tryCount by one:
tryCount++;
try {
// Try to execute our callback:
return await callback();
} catch (error) {
// If our callback throws any error lets check it:
if (filterError(error) && tryCount <= maxRetries) {
// Recursively call this IIFY to repeat
return retry();
}
// Otherwise rethrow the error:
throw error;
}
})();
}
Демо
Попробуйте 2 раза:
await asyncRetry(async () => {
// Put your async code here
}, { maxRetries = 2 })
Попробуйте 2 раза и повторите попытку DOMError
s:
await asyncRetry(async () => {
// Put your async code here
}, {
maxRetries = 2,
filterError: (error) => error instance of DOMError
})
Infine Retry: (Не делайте этого!)
await asyncRetry(async () => {
// Put your async code here
})
Одна библиотека может сделать это легко: обещание-повтор.
Вот несколько примеров, чтобы проверить это:
const promiseRetry = require('promise-retry');
Ожидайте вторую попытку быть успешной:
it('should retry one time after error', (done) => {
const options = {
minTimeout: 10,
maxTimeout: 100
};
promiseRetry((retry, number) => {
console.log('test2 attempt number', number);
return new Promise((resolve, reject) => {
if (number === 1) throw new Error('first attempt fails');
else resolve('second attempt success');
}).catch(retry);
}, options).then(res => {
expect(res).toBe('second attempt success');
done();
}).catch(err => {
fail(err);
});
});
Ожидайте только одну попытку:
it('should not retry a second time', (done) => {
const options = {
retries: 1,
minTimeout: 10,
maxTimeout: 100
};
promiseRetry((retry, number) => {
console.log('test4 attempt number', number);
return new Promise((resolve, reject) => {
if (number <= 2) throw new Error('attempt ' + number + ' fails');
else resolve('third attempt success');
}).catch(retry);
}, options).then(res => {
fail('Should never success');
}).catch(err => {
expect(err.toString()).toBe('Error: attempt 2 fails');
done();
});
});
Простая повторная попытка обещания:
function keepTrying(otherArgs, promise) {
promise = promise||new Promise();
// try doing the important thing
if(success) {
promise.resolve(result);
} else {
setTimeout(function() {
keepTrying(otherArgs, promise);
}, retryInterval);
}
}
Единственная простая в использовании и чистая Javascript с нулевой зависимостью , которая вам когда-либо понадобится.
Пример
const { retry } = require('@ajimae/retry')
function exec() {
// This will be any async or sync action that needs to be retried.
return new Promise(resolve => {
setTimeout(() => {
resolve({ message: 'some async data' })
}, 1500)
})
}
// takes the response from the exec function and check if the condition/conditions are met
function predicate(response, retryCount) {
console.log(retryCount) // goes from 0 to maxRetries
// once this condition is met the retry exits
return (response.message == 'some async data')
}
(async function main() {
// enable or disable an exponential backoff behaviour if needed.
const result = await retry(exec, predicate, { maxRetries: 5, backoff: true })
console.log(result) // { message: 'some async data' }
})()
PS: Я является автором этой библиотека асинхронных повторовбиблиотеки .