Promessa repetir os padrões de design

Editar

  1. Padrão que continua tentando até que a promise seja resolvida (com delay e maxRetries).
  2. Padrão que continua tentando até que a condição atinja o resultado (com delay e maxRetries).
  3. Um padrão dynamic eficiente de memory com tentativas ilimitadas (atraso fornecido).

Código para o nº 1 Continua tentando novamente até que a promise seja resolvida (alguma comunidade de melhorias para a linguagem, etc.)

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(); }); }; 

Usar

 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); 

O código para o nº 2 continua tentando até que uma condição se encontre no resultado then resultando em um modo reutilizável (condição é o que irá variar).

 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); 

    Algo um pouco diferente …

    As tentativas assíncronas podem ser obtidas através da construção de uma cadeia .catch() , em oposição à cadeia mais usual .then() .

    Essa abordagem é:

    • possível apenas com um número máximo especificado de tentativas. (A corrente deve ter comprimento finito)
    • somente aconselhável com um máximo baixo. (Cadeias de promise consomem memory aproximadamente proporcional ao seu comprimento).

    Caso contrário, use uma solução recursiva.

    Primeiro, uma function de utilidade para ser usada como callback .catch() .

     var t = 500; function rejectDelay(reason) { return new Promise(function(resolve, reject) { setTimeout(reject.bind(null, reason), t); }); } 

    Agora você pode construir cadeias de captura de forma concisa:

    1. Tente novamente até que a promise seja resolvida, com atraso

     var max = 5; var p = Promise.reject(); for(var i=0; i 

    DEMO : https://jsfiddle.net/duL0qjqe/

    2. Tente novamente até que o resultado atenda a alguma condição, sem atraso

     var max = 5; var p = Promise.reject(); for(var i=0; i 

    DEMO : https://jsfiddle.net/duL0qjqe/1/

    3. Tente novamente até que o resultado atenda a alguma condição, com atraso

    Tendo a sua mente em volta (1) e (2), um teste combinado + atraso é igualmente trivial.

     var max = 5; var p = Promise.reject(); for(var i=0; i 

    test() pode ser síncrono ou asynchronous.

    Também seria trivial adicionar mais testes. Simplesmente sanduíche uma cadeia de thens entre as duas capturas.

     p = p.catch(attempt).then(test1).then(test2).then(test3).catch(rejectDelay); 

    DEMO : https://jsfiddle.net/duL0qjqe/3/


    Todas as versões são projetadas para attempt ser uma function assíncrona de retorno de promise. Também poderia retornar um valor, caso em que a cadeia seguiria seu caminho de sucesso para o próximo / terminal .then() .

    Você pode encadear uma nova promise sobre a anterior, atrasando assim sua eventual resolução até que você saiba a resposta final. Se a próxima resposta ainda não for conhecida, encadeie outra promise sobre ela e continue encadeando checkStatus () para si mesma até que, eventualmente, você saiba a resposta e possa retornar a resolução final. Isso poderia funcionar assim:

     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); 

    Note, eu também evitei criar a promise em torno da sua declaração switch . Como você já está em um manipulador .then() , apenas retornar um valor é resolver, lançar uma exceção é rejeitar e retornar uma promise está encadeando uma nova promise na anterior. Isso abrange os três ramos da instrução switch sem criar uma nova promise. Por conveniência, eu uso uma function delay() baseada em promise.

    FYI, isso pressupõe que o work.requestStatus() não precisa de argumentos. Se ele precisar de alguns argumentos específicos, você pode passá-los no ponto da chamada de function.


    Também pode ser uma boa ideia implementar algum tipo de valor de tempo limite por quanto tempo você passará aguardando a conclusão para que isso nunca continue indefinidamente. Você pode adicionar a funcionalidade de tempo limite assim:

     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); 

    Não sei exatamente qual “padrão de design” você está procurando. Já que você parece se opor à function checkStatus() declarada checkStatus() , aqui está uma versão em linha:

     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); 

    Um esquema de repetição mais reutilizável que poderia ser usado em muitas circunstâncias definiria algum código externo reutilizável, mas você parece se opor a isso, então eu não fiz essa versão.


    Aqui está uma outra abordagem que usa um método .retryUntil() no Promise.prototype acordo com sua solicitação. Se você quiser ajustar detalhes de implementação, você deve poder modificar esta abordagem geral:

     // 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); 

    Eu ainda não posso realmente dizer o que você quer ou o que acontece com todas essas abordagens não é resolver o seu problema. Como suas abordagens parecem estar todas em código inline e não usam um ajudante resuable, aqui está uma delas:

     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); 

    2. Padrão que continua tentando até que a condição atinja o resultado (com delay e maxRetries)

    Esta é uma maneira legal de fazer isso com promises nativas de maneira recursiva:

     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); }); }); 

    É assim que você chama, assumindo que func algumas vezes é bem-sucedido e às vezes falha, sempre retornando uma string que podemos registrar:

     retryOperation(func, 1000, 5) .then(console.log) .catch(console.log); 

    Aqui estamos chamando retryOperation pedindo para tentar novamente a cada segundo e com o máximo de tentativas = 5.

    Se você quiser algo mais simples sem promises, o RxJs ajudaria com isso: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/retrywhen.md

     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); 

    Uma biblioteca pode fazer isso facilmente: prometer-repetir .

    Aqui estão alguns exemplos para testá-lo:

     const promiseRetry = require('promise-retry'); 

    Espere segunda tentativa para ser bem sucedido:

     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); }); }); 

    Espere apenas uma tentativa:

     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(); }); }); 

    Há muitas boas soluções mencionadas e agora com async / aguarde estes problemas podem ser resolvidos sem muito esforço.

    Se você não se importa com uma abordagem recursiva, então esta é a minha solução.

     function retry(fn, retries=3, err=null) { if (retries === 0) { return Promise.reject(err); } return fn().catch(err => { return retry(fn, (retries - 1), err); }); }