В javascript функция, которая возвращает обещание и повторяет лучшие практики внутреннего асинхронного процесса.

У меня есть функция, которая возвращает обещание javascript, и внутри нее выполняется некоторый асинхронный код. Асинхронный код необходимо повторить пару раз в случае сбоя. Я делал это, пока не заметил странное поведение, которое заставило меня задуматься, правильно ли я это делаю. Так что мне пришлось изменить его. Оба подхода обрезаны здесь. У меня есть некоторое представление о том, почему первый подход (asyncFunc) не работает, и я был бы признателен, если бы кто-нибудь поделился технической ясностью по этому поводу. А для второго подхода (ayncFunc_newer) есть предложения, как это можно сделать лучше?

var _retryCount = 0;

// this is what I was doing
function asyncFunc () {
	return new Promise(function(fulfill, reject) {
		doAsync()
			.then(fulfill)
			.catch(retry);

		function retry(promiseResult) {
			if(_retryCount < 3) {
				_retryCount++;
				return asyncFunc();
			}
			else {
				reject(promiseResult);
			}
		}
	});
}

// this what I'm doing now
function ayncFunc_newer() {
    return new Promise(function(fulfill, reject) {
        var retryCount = 0;

        doAsync()
            .then(fulfill)
            .catch(onReject);

        function onReject(bmAuthError) {
            if(retryCount < 3) {
                retryCount++;
                logWarning(error);
                
                doAsync()
                	.then(fulfill)
                	.catch(onReject);
            }
            else {
                fulfill(false);
            }
        }
    });                 
};


person Abtin    schedule 24.07.2015    source источник
comment
Где вы увеличиваете _retryCount ?   -  person juju    schedule 24.07.2015
comment
Забыл его. Обновлен код   -  person Abtin    schedule 24.07.2015


Ответы (1)


Лучше всего избегать анти-шаблона конструктора промисов. По сути, new Promise существует в основном для обертывания API-интерфейсов без обещаний, поэтому, если ваши функции уже возвращают обещания, обычно есть способ избежать его использования.

Если вы выполняете небольшое фиксированное число повторных попыток, то ваш случай так же прост, как:

function ayncFunc() {
  return doAsync().catch(doAsync).catch(doAsync).catch(doAsync);
};

Для настраиваемого количества повторных попыток вы должны расширить это до:

var retries = 3;

function ayncFunc() {
  var p = doAsync();
  for (var i = 0; i < retries; i++) {
    p = p.catch(doAsync);
  }
  return p;
};

Или для большего количества попыток вы можете использовать рекурсивный подход:

function ayncFunc() {
  function recurse(i) {
    return doAsync().catch(function(e) {
      if (i < retries) {
        return recurse(++i);
      }
      throw e;
    });
  }
  return recurse(0);
};

var console = { log: msg => div.innerHTML += msg + "<br>" };

function doAsync() {
  console.log("doAsync");
  return Promise.reject("Nope");
}

function ayncFunc() {
  return doAsync().catch(doAsync).catch(doAsync).catch(doAsync);
};

var retries = 3;

function ayncFunc2() {
  var p = doAsync();

  for (var i=0; i < retries; i++) {
    p = p.catch(doAsync);
  }
  return p;
};

function ayncFunc3() {
  function recurse(i) {
    return doAsync().catch(function(e) {
      if (i < retries) {
        return recurse(++i);
      }
      throw e;
    });
  }
  return recurse(0);
};

ayncFunc().catch(function(e) { console.log(e); })
.then(ayncFunc2).catch(function(e) { console.log(e); })
.then(ayncFunc3).catch(function(e) { console.log(e); });
<div id="div"></div>

person jib    schedule 25.07.2015
comment
Я бы скорее рекомендовал использовать рекурсивный подход, чем строить очень длинные цепочки. - person Bergi; 25.07.2015
comment
@Bergi Я сомневаюсь, что накладные расходы велики, поскольку обработчики catch пропускаются в обычном (успешном) потоке кода, но я добавил рекурсивный пример для полноты картины. Спасибо за идею. - person jib; 26.07.2015
comment
@jib в рекурсивном подходе сначала возвращается промис, а в случае исключения вызываемому объекту возвращается другой промис. Разве это не смущает вызывающего абонента ayncFunc? - person Abtin; 28.07.2015
comment
@Abtin вызывающий абонент asyncFunc всегда видит только одно обещание, которое немедленно возвращается из doAsync().catch(), но это обещание вскоре становится разрешенным (или, может быть, лучше сказать, заблокированным, поскольку reject был вызван на нем) еще одно ожидающее обещание, возвращенное из второго вызова recurse(). Первое обещание остается ожидающим и не будет установлено (будет выполнено или отклонено), пока второе обещание не будет выполнено, наследуя его ценность. Здесь используется терминология в состояния и судьбы< /а>. - person jib; 28.07.2015
comment
@jib спасибо, теперь я понимаю этот сценарий. Теперь я запутался в идеальном сценарии, когда doAsync выполняется с первой попытки. Вызывающий asyncFunc имеет обещание doAsync().catch(), как это выполняется в этом сценарии? - person Abtin; 28.07.2015
comment
@Абтин - так же. .catch(y) — это просто псевдоним для .then(undefined, y) который поведенчески эквивалентен .then(x => x, y). Выполнение и отклонение работают довольно симметрично, и обещание, возвращаемое либо .catch, либо .then, является просто еще одним звеном в цепочке обещаний. - person jib; 28.07.2015
comment
Во втором примере не будет ли doAsync передавать ошибку в качестве параметра? Разве это не становится намного сложнее, если вашей асинхронной рабочей функции требуются параметры? - person Tony Gutierrez; 03.11.2017
comment
@TonyGutierrez Да, для простоты я основывал примеры на doAsync() в вопросе. С аргументами сделать p = p.catch(() => doAsync(...arguments)); - person jib; 09.11.2017
comment
Хорошая практика, спасибо! Кстати, можно ли установить временную задержку между каждой повторной попыткой? Что-то вроде следующего: function ayncFunc() { function recurse(i) { return doAsync().catch(function(e) { if (i ‹ retries) { setTimeout(()=›{ return recurse(++i); } , 1000); // вернуть recurse(++i); } throw e; }); } вернуть рекурсию (0); }; Вероятно, это не сработает, так как результат будет возвращен (независимо от того, ошибка это или нет) с первой попытки... - person mike652638; 16.04.2020
comment
Хорошо, вдохновленный другим ответом в stackoverflow.com/questions/38213668/, я придумал компрометирующее решение, такое как function doAsync(e) { return new Promise(function(resolve, reject) { setTimeout(reject.bind(null, e), t); }); } - person mike652638; 16.04.2020