Search code examples
promisebluebirdcancellation

Cancel a promise from the 'then'


I just switched to promises and I'm sure I got something wrong but I can't figure out what. In the following code, I'd like to cancel the promise if _getDevice() returns null (it's a mongodb.findOneAsync() under the hood).

From my perspective if _getDevice returns null, the promise should be cancelled and I should see the 'CANCEL REPLY' log. But instead I'm getting the 'SAVE REPLY' log.

I'm using the bluebird library.

var promise = Promise.props({
    device: _getDevice(event.deviceId),
    events: _getEvents(event.deviceId, event.date)
}).cancellable();

promise.then(function (result) {
    if (! result.device) {
        return promise.cancel()
    }

    var device = new Device(result.device);
    var events = result.events;

    // ...

    return db.collection(collections.devices).saveAsync(device.toMongoDocument());
}).then(function () {
    console.log('SAVE REPLY');
    return req.reply(null);
}).catch(Promise.CancellationError, function (err) {
    console.log('CANCEL REPLY')
    return req.reply(null);
}).catch(function (error) {
    return req.reply(error);
});

Solution

  • You cannot cancel a promise from the then callback, as the execution of that callback means that the promise has been resolved. It's impossible to change the state afterwards.

    What you probably want to do is throw an error from the handler, resulting in the rejection of the new promise that was returned from then().

    You even might do that before the two promises are joined, so that the getEvents promise is cancelled when the getDevice promise fails:

    Promise.props({
        device: _getDevice(event.deviceId).then(function(device) {
            if (!device)
                throw new Promise.CancellationError(); // probably there's a better error type
            else
                return new Device(device);
        },
        events: _getEvents(event.deviceId, event.date) // assuming it is cancellable
    }).then(function (result) {
        var device = result.device;
        var events = result.events;
    
        // ...
    
        return db.collection(collections.devices).saveAsync(device.toMongoDocument());
    }).then(function () {
        console.log('SAVE REPLY');
        return req.reply(null);
    }).catch(Promise.CancellationError, function (err) {
        console.log('CANCEL REPLY')
        return req.reply(null);
    }).catch(function (error) {
        return req.reply(error);
    });