brandonscript
brandonscript

Reputation: 72895

Update 'this' from callback when returning function called with .apply()

I've got a ProjectClient class that contains a method (.GET()) for making HTTP calls. It supports some arguments and a callback in the same manner as , but has some complex url and header building functions that happens behind the scenes too:

client.GET(idOrDataObject, function(err, customResponse) {
    // url is built based on the id or dataObject passed as the first param
})

On a successful response, one of the object types that comes back is a DataObject:

client.GET(idOrDataObject, function(err, customResponse) {
    console.log(customResponse.dataObject instanceof DataObject) // true
})

I'm adding a convenience method to the DataObject class called reload() that calls back to the client.GET() method and reloads itself, but I'd like to be able to update this with the new version of DataObject that's returned from the server:

DataObject.prototype.reload = function() {
    var args = Array.prototype.slice.call(arguments) // extracts all arguments
    var client = helpers.client.validate(args) // ensures 'client' was passed in the args array before continuing
    args.unshift(this) // prepends 'this' (the DataObject instance)

    // How can I update 'this' with the response contained in the 
    // callback passed (the last element in 'args')?
    return client.GET.apply(client, args)
}

Usage would look like this:

client.GET(idOrDataObject, function(err, customResponse) {
    var o = customResponse.dataObject
    // assume something changed on the server
    o.reload(function(err, done) {
         // o has now been updated with the latest copy from the server
    })
})

Update:

Starting to think the only way this will work is if I hijack the callback further down the chain, e.g. inside client.GET:

var DataObject = require('../lib/dataObject')
var request = require('request')
var _ = require('lodash')

var GET = function(client, args) {
    var options = {
        client: client
    }

    // if a preformatted options argument passed, assign it to options
    if (_.isObject(args[0]) && !_.isFunction(args[0]) && args.length <= 2) {
        _.assign(options, args[0])
    }

    options.callback = helpers.cb(_.last(args.filter(_.isFunction)))
    options.type = _.first([options.type, args[0]._type, args[0]].filter(_.isString))
    options.dataObject = _.first([options.dataObject, args[0]].filter(function(property) {
        return (property instanceof DataObject)
    }))

    request('http://...', {
        body: options.body,
        json: true,
        method: 'GET'
    }, function(error, response) {
        var customResponse = new CustomResponse(response)
        if (options.dataObject instanceof DataObject) {
            options.dataObject = customResponse.dataObject
            // here I see both 'options.dataObject' and 'customResponse.dataObject' have the correct value for reloadTest
            console.log('updated', options.dataObject.reloadTest, customResponse.dataObject.reloadTest)
        }
        options.callback(error, customResponse, customResponse.dataObject)
    })
}

But even doing this, the original copy of dataObject isn't being updated - it's as if it was cloned or duplicated and isn't a pointer to the original?

Here's a test that's proving the failure. How can I ensure that the correct pointer to dataObject is passed?

var now = Date.now()
client.GET('test', function(err, getResponse) {
    var dataObject = new DataObject(getResponse.dataObject)
    getResponse.dataObject.reloadTest = now // 1452109996140

    console.log('now', now, 'getResponse.dataObject.reloadTest', getResponse.dataObject.reloadTest)
    // now 1452109996140 getResponse.dataObject.reloadTest 1452109996140

    client.PUT(getResponse.dataObject, function(err, putResponse) {
        // updated 1452109996140 1452109996140

        console.log('putResponse.dataObject.reloadTest', putResponse.dataObject.reloadTest)
        // putResponse.dataObject.reloadTest 1452109996140

        dataObject.reload(function(err, response) {
            // updated 1452109996140 1452109996140

            console.log('done', dataObject.reloadTest, 'response.dataObject.reloadTest', response.dataObject.reloadTest)
            // done 1452109916111 response.dataobject.reloadTest 1452109996140
        })
    })
})

Upvotes: 2

Views: 75

Answers (1)

nrabinowitz
nrabinowitz

Reputation: 55678

The short answer is, you can't. You can't actually swap the object for another object. Some options:

  • Use a wrapper object, and give that the reload method:

    client.GET(idOrDataObject, function(err, customResponse) {
        var o = new DataWrapper(customResponse);
        o.dataObject; // old data
        // assume something changed on the server
        o.reload(function(err, done) {
             o.dataObject; // new data
        });
    });
    
  • Update the data in the dataObject, keeping the same object as before:

    DataObject.prototype.reload = function(callback) {
        client.GET(this, function(err, customResponse) {
            // Assign all properties from the response to the existing obj
            // Could also use $.extend, _.extend, etc
            Object.assign(this, customResponse.dataObject);
            callback(this);
        }.bind(this));
    }
    
  • Just pass the new object in the callback and ignore the old object:

    DataObject.prototype.reload = function(callback) {
        client.GET(this, function(err, customResponse) {
            callback(customResponse.dataObject);
        });
    }
    

Upvotes: 1

Related Questions