1

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?

zangw
  • 43,869
  • 19
  • 177
  • 214
  • have you tried ```.then( this.handleDisconnections_ ).then(this.publish_, this.reconnect_)```? – mido Feb 11 '15 at 01:16
  • @mido22 - but probably need something that doesn't keep this loop going forever retrying every 3 seconds. Perhaps a max number of retries before giving up. – jfriend00 Feb 11 '15 at 01:23
  • @jfriend00 how about my answer now, I have added a parameter to take care of the number of attempts... – mido Feb 11 '15 at 01:31
  • The `catch` processes the promise "successfully" (unless it itself throws), which means chained `then`'s (including the `done`) will operate on the fulfilled/resolved result of `reconnect_`. If you want the promise to "remain" in rejected state, one simple way is to rethrow the error inside `reconnect_`. –  Feb 11 '15 at 05:55

3 Answers3

2

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);
        });
};
mido
  • 24,198
  • 15
  • 92
  • 117
2

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.

Esailija
  • 138,174
  • 23
  • 272
  • 326
1

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 );
        });
};
zangw
  • 43,869
  • 19
  • 177
  • 214