1
0
Fork 0

AutoRetry delay implementation

master
Mike Gerwitz 2015-05-22 16:22:58 -04:00
parent ca5d064455
commit af4a775155
3 changed files with 155 additions and 29 deletions

View File

@ -40,10 +40,10 @@ module.exports = Trait( 'AutoRetry' )
'private _tries': 0,
/**
* Delay in milliseconds before making the nth request as a function
* of n
* Function to be passed a continuation to introduce a delay between
* requests
*
* @var {function(number): number}
* @var {function(number,function(),function())} delay
*/
'private _delay': null,
@ -52,33 +52,42 @@ module.exports = Trait( 'AutoRetry' )
* Initialize auto-retry
*
* If TRIES is negative, then requests will continue indefinitely until
* one succeeds. If TRIES is 0, then no requests will be performed.
* one succeeds or is aborted by DELAY. If TRIES is 0, then no requests
* will be performed.
*
* @param {function(?Error,*): boolean} pred predicate determining if
* a retry is needed
* @param {number} tries maximum number of tries,
* including the initial
* request
* @param {function(number): number} delay delay in milliseconds before
* making the nth request as
* a function of n
* If DELAY is a function, then it invoked with a retry continuation
* before each retry, the number of tries remaining, and a failure
* continuation that may be used to abort the process at an arbitrary
* time.
*
* @return {undefined}
*/
* @param {function(?Error,*): boolean} pred predicate determining if
* a retry is needed
* @param {number} tries maximum number of tries,
* including the initial
* request
*
* @param {?function(number,function(),function())} delay
* an optional function
* accepting a continuation
* to continue with the next
* request
*
* @return {undefined}
*/
__mixin: function( pred, tries, delay )
{
if ( typeof pred !== 'function' )
{
throw TypeError( 'Predicate must be a function' );
}
if ( typeof delay !== 'function' )
if ( delay && ( typeof delay !== 'function' ) )
{
throw TypeError( "Delay must be a function" );
}
this._pred = pred;
this._tries = +tries;
this._delay = delay;
this._delay = delay || function( _, c ) { c(); };
},
@ -96,9 +105,8 @@ module.exports = Trait( 'AutoRetry' )
* data from the final request are returned.
*
* If the number of tries is negative, then requests will be performed
* indefinitely until success.
*
* TODO: A means of aborting.
* indefinitely until success; the delay function (as provided via the
* constructor) may be used to abort in this case.
*
* @param {string} input binary data to transmit
* @param {function(?Error,*)} callback continuation upon reply
@ -144,14 +152,26 @@ module.exports = Trait( 'AutoRetry' )
return _self._succeed( output, callback );
}
var fail = function()
{
_self._fail( err, output, callback );
};
// note that we intentionally do not want to check <= 1, so that
// we can proceed indefinitely (JavaScript does not wrap on overflow)
if ( n === 1 )
{
return _self._fail( err, output, callback );
return fail();
}
_self._try( input, callback, ( n - 1 ) );
_self._delay(
( n - 1 ),
function()
{
_self._try( input, callback, ( n - 1 ) );
},
fail
);
} );
},
@ -184,4 +204,3 @@ module.exports = Trait( 'AutoRetry' )
callback( err, output );
},
} );

View File

@ -194,4 +194,3 @@ module.exports = Class( 'XhrHttpImpl' )
);
}
} );

View File

@ -42,7 +42,7 @@ describe( 'dapi.AutoRetry trait', function()
// success (but note the number of retries presented)
var stub = _createStub( null, '' )
.use( Sut( _void, 5, _void ) )
.use( Sut( _void, 5 ) )
();
stub.request( given, function()
@ -62,7 +62,7 @@ describe( 'dapi.AutoRetry trait', function()
// notice that we provide an error to the stub; this will ensure
// that the returned error is null even when one is provided
var stub = _createStub( {}, chk )
.use( Sut( _void, 1, _void ) )
.use( Sut( _void, 1 ) )
();
stub.request( '', function( err, data )
@ -85,7 +85,7 @@ describe( 'dapi.AutoRetry trait', function()
var n = 5;
var stub = _createStub( {}, {} )
.use( Sut( _true, n, _void ) )
.use( Sut( _true, n ) )
();
stub.request( {}, function( err, _ )
@ -104,7 +104,7 @@ describe( 'dapi.AutoRetry trait', function()
// XXX: this does not test for most recent, because the return
// data are static for each request
var stub = _createStub( e, output )
.use( Sut( _true, 1, _void ) )
.use( Sut( _true, 1 ) )
();
stub.request( {}, function( err, data )
@ -127,7 +127,7 @@ describe( 'dapi.AutoRetry trait', function()
};
var stub = _createStub()
.use( Sut( pred, -1, _void ) )
.use( Sut( pred, -1 ) )
();
stub.request( {}, function( _, __ )
@ -145,7 +145,7 @@ describe( 'dapi.AutoRetry trait', function()
it( 'will perform zero requests with null results', function( done )
{
var stub = _createStub( {}, {} )
.use( Sut( _void, 0, _void ) )
.use( Sut( _void, 0 ) )
();
stub.request( {}, function( err, data )
@ -157,6 +157,114 @@ describe( 'dapi.AutoRetry trait', function()
} );
} );
} );
describe( 'when a delay function is provided', function()
{
it( 'will wait for continuation before retry', function( done )
{
var waited = false;
var wait = function( _, c )
{
waited = true;
c();
};
var stub = _createStub( {}, {} )
.use( Sut( _true, 2, wait ) )
();
stub.request( {}, function( _, __ )
{
expect( waited ).to.equal( true );
done();
} );
} );
it( 'will not process if continuation is not called', function()
{
var waited = false;
var wait = function( _, c )
{
waited = true;
/* do not invoke */
};
var stub = _createStub( {}, {} )
.use( Sut( _true, 2, wait ) )
();
// this works because we know that our stub is invoked
// synchronously
stub.request( {}, function( _, __ )
{
throw Error( "Should not have been called!" );
} );
expect( waited ).to.equal( true );
} );
it( 'will call delay function until success', function()
{
var n = 5;
var wait = function( tries_left, c )
{
n--;
// the first argument is the number of tries left
expect( tries_left ).to.equal( n );
c();
};
var pred = function()
{
return n > 0;
};
var stub = _createStub( {}, {} )
.use( Sut( pred, n, wait ) )
();
// this works because we know that our stub is invoked
// synchronously
stub.request( {}, _void );
// the first request counts as one, which brings us down to 4,
// but the wait function has not been called at this point; so,
// we expect that it will only be called four times
expect( n ).to.equal( 1 );
} );
it( 'allows aborting via failure continuation', function( done )
{
var err_expect = {},
out_expect = [];
var wait = function( _, __, abort )
{
abort();
};
// without aborting, this would never finish
var stub = _createStub( err_expect, out_expect )
.use( Sut( _true, -1, wait ) )
();
// this works because we know that our stub is invoked
// synchronously
stub.request( {}, function( err, output )
{
expect( err ).to.equal( err_expect );
expect( output ).to.equal( out_expect );
done();
} );
} );
} );
} );