2

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
        })
    })
})
brandonscript
  • 68,675
  • 32
  • 163
  • 220
  • `reload` only takes one argument (a callback) that you've shown, so I'm unclear why you have several lines dealing `arguments`. What other arguments do you expect to be passed into `reload`? – apsillers Jan 06 '16 at 19:26
  • don't unshift, then simply `return client.GET.apply(this, args)` – dandavis Jan 06 '16 at 19:28
  • `reload` only takes one, but `GET` takes several. Rather than fight with what is or isn't there, I'm just passing args. Simple enough to get callback out of args though (using lodash, `_.last(args)`). – brandonscript Jan 06 '16 at 19:28
  • @dandavis have to unshift because client.GET takes dataObject as a first parameter. Updated my pseudo code for .GET to make that more clear. – brandonscript Jan 06 '16 at 19:39

1 Answers1

1

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);
        });
    }
    
nrabinowitz
  • 55,314
  • 10
  • 149
  • 165
  • Have a look at my update - I think your second suggestion is on the right track, but since I'm already passing it down the chain, I'd like to see if I can get it working with the implementation I've already got? – brandonscript Jan 06 '16 at 20:06
  • I think you're misunderstanding how JS references work. You can change `options.dataObject`, but it's changing a property of `options` to point to a new object, not updating the old object. Unless your calling code is looking at `options`, it won't work. If you go the second route, you have to modify the object, *not* try to reassign the reference. See http://stackoverflow.com/questions/6605640/javascript-by-reference-vs-by-value/6605700#6605700 – nrabinowitz Jan 06 '16 at 20:11
  • Ahh, yes you're completely right, I missed that entirely. Option 2 was definitely the simplest approach. – brandonscript Jan 06 '16 at 20:16