zangw
zangw

Reputation: 48366

Recursive retry in Promise

Now I am learning how to write JavaScript code with promise. Here is my case, the deliverMessage function in Sender try to connect with amqp. If success, then call publish_ to send messages. Otherwise, call reconnect_ to reconnect to amqp after 3 seconds. The codes are as following,

Sender.prototype.reconnect_ = function( err ) {
    console.error('MessageBus disconnected, attempting to reconnect' + err);
    this.createFakeChannel_();
    return setTimeout( this.deliverMessage.bind(this), 3000);
};
Sender.prototype.deliverMessage = function() {
    when(amqp.connect( this.addr_ ))
        .with( this )
        .then( this.createChannel_ )
        .then( this.createExchange_ )
        .then( this.handleUnrouteableMessages_ )
        .then( this.handleDisconnections_ )
        .catch( this.reconnect_ )
        .done( this.publish_ ); //? publish_ is invoked in all case?
};

Actually, whether the connection is succeeded or failed, publish_ is called anyway. Could anyone help me how to implement it with promise?

Upvotes: 1

Views: 1551

Answers (3)

zangw
zangw

Reputation: 48366

Quoting here

Implementing a retry pattern is fairly simple with promises and recursion. The key is ensuring that the promise chain is unbroken. In your example, the call to setTimeout severs the promise chain by initiating a new asynchronous call to deliveryMessage that is outside the promise chain. reconnect also returns the the result of setTimeout immediately.

My codes are changed as following

Sender.prototype.deliverMessage = function ( key, msg ) {
    return this
        .tryConnect_( this.attempts_, this.retryDelay_ )
        .with(this)
        .then(function() {
            return this.publish_( key, msg );
        }).catch( function( e ) {
            console.log( e );
        });
}

Sender.prototype.retryConnect_ = function( attempts, retryDelay, err ) {
    if (attempts === 0) {
        console.error('Sender: MessageBus disconnected, attempted to reconnect. Err:' + err);
        return when.reject( new Error('Max reconnect attempts exceeded, connection failed'));
    }

    return when( 'retry' )
        .with( this )
        .delay( retryDelay )
        .then(function() { 
            return this.tryConnect_( attempts - 1, this.retryDelay_ ); 
        })
}

Sender.prototype.tryConnect_ = function( attempts, retryDelay ) {
    return when(amqp.connect( this.addr_ ))
        .with( this )
        .then( this.createChannel_ )
        .then( this.createExchange_ )
        .then( this.handleUnrouteableMessages_ )
        .then( this.handleDisconnections_ )
        .catch( function( e ) {
            return this.retryConnect_( attempts, retryDelay, e );
        });
};

Upvotes: 1

Esailija
Esailija

Reputation: 140210

setTimeout doesn't return a promise so that's not gonna work.

Sender.prototype.reconnect_ = function( err ) {
    console.error('MessageBus disconnected, attempting to reconnect' + err);
    this.createFakeChannel_();
    return when.delay(3000).with(this).then(this.deliverMessage);
};
Sender.prototype.deliverMessage = function () {
    when(amqp.connect( this.addr_ ))
        .with( this )
        .then( this.createChannel_ )
        .then( this.createExchange_ )
        .then( this.handleUnrouteableMessages_ )
        .then( this.handleDisconnections_ )
        .then( this.publish_ )
        .catch( this.reconnect_ );
};

Your placement of done was wrong (in fact you should never use done with when.js anyway but that's another story), it will always be called as you say.

Upvotes: 2

mido
mido

Reputation: 25034

I would do that like...

Sender.prototype.reconnect_ = function( err, attempt ) {
    attempt = attempt || 0;
    attempt++;
    if(attempt>3){  // change it to whatever value you prefer
        throw err;
    }
    console.error('MessageBus disconnected, attempting to reconnect' + err);
    this.createFakeChannel_();
    return setTimeout( this.deliverMessage.bind(this, attempt ), 3000);
};
Sender.prototype.deliverMessage = function(attempt) {
    when(amqp.connect( this.addr_ ))
        .with( this )
        .then( this.createChannel_ )
        .then( this.createExchange_ )
        .then( this.handleUnrouteableMessages_ )
        .then( this.handleDisconnections_ )
        .then( this.publish_, function(err){
            this.reconnect_(err, attempt);
        });
};

Upvotes: 2

Related Questions