Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Freshbooks Sync calls need making asynchronous #1

Open
mrjcleaver opened this issue Mar 8, 2012 · 5 comments
Open

Freshbooks Sync calls need making asynchronous #1

mrjcleaver opened this issue Mar 8, 2012 · 5 comments

Comments

@mrjcleaver
Copy link
Owner

Node never waits for IO.

So this call to

node_modules/freshbooks-javascript-library/library/FreshBooks/Element.js-

  return httpClient.send(requestXML); 

will not wait and the return must be decoupled into a callback.

So, node_modules/freshbooks-javascript-library/library/FreshBooks/Element.js:

FreshBooks_Element.prototype.sendRequest = function(content,methodName){
    this.lastError = "";
    var requestXML = this.requestEnvelope(content,methodName);
    return httpClient.send(requestXML); 
}

needs to be changed to accept a callback on the method signature and to pass that callback down to httpClient.send:

FreshBooks_Element.prototype.sendRequest = function(content,methodName, callback){
    this.lastError = "";
    var requestXML = this.requestEnvelope(content,methodName);
    httpClient.send(requestXML, callback); 
}

As well as affecting httpClient.send, it affects Element.js, BaseInvoice.js and Invoice.js:

grep -C4 -i -r sendRequest node_modules/freshbooks-javascript-library/library/FreshBooks/Element.js

/**
* send request to the server
*/  
FreshBooks_Element.prototype.sendRequest = function(content,methodName){
this.lastError = "";
var requestXML = this.requestEnvelope(content,methodName);
return httpClient.send(requestXML); 
}
--
*/          
FreshBooks_Element.prototype.create = function(){
var content = "";
content = this.internalPrepareCreate(content);
var responseXML = this.sendRequest(content,"create");
var responseStatus = this.processResponse(responseXML);
this.internalCreate(responseStatus,responseXML);
return responseStatus;
}
--
*/          
FreshBooks_Element.prototype.update = function (){
var content = "";
content = this.internalPrepareUpdate(content);
var responseXML = this.sendRequest(content,"update");
var responseStatus = this.processResponse(responseXML);
this.internalUpdate(responseStatus,responseXML);
return responseStatus;
}
--
*/          
FreshBooks_Element.prototype.get = function(id){
var content = "";
content = this.internalPrepareGet(id,content);
var responseXML = this.sendRequest(content,"get");
var responseStatus = this.processResponse(responseXML);
this.internalGet(responseStatus,responseXML);
return responseStatus;
}
--
*/          
FreshBooks_Element.prototype.del = function(){
var content = "";
content = this.internalPrepareDelete(content);
var responseXML = this.sendRequest(content,"delete");
var responseStatus = this.processResponse(responseXML);
this.internalDelete(responseStatus,responseXML);
return responseStatus;
}
--
*/          
FreshBooks_Element.prototype.listing = function(page,perPage,filters){
var content = this.getTagXML("page",page) + this.getTagXML("per_page",perPage);
content = content + this.internalPrepareListing(filters,content);
var responseXML = this.sendRequest(content,"list");
var responseStatus = this.processResponse(responseXML);
return this.internalListing(responseStatus,responseXML);
}

node_modules/freshbooks-javascript-library/library/FreshBooks/BaseInvoice.js:


FreshBooks_BaseInvoice.prototype.sendByEmail = function(){
var content = "";
content = this.internalPrepareSendByEmail(content);
var responseXML = this.sendRequest(content,"sendByEmail");
var responseStatus = this.processResponse(responseXML);
this.internalSendByEmail(responseStatus,responseXML);
return responseStatus;
}


node_modules/freshbooks-javascript-library/library/FreshBooks/Invoice.js:
FreshBooks_Invoice.prototype.sendBySnailMail = function(){
var content = this.getTagXML("invoice_id",this.invoiceId);
var responseXML = this.sendRequest(content,"sendBySnailMail");
var responseStatus = this.processResponse(responseXML);
return responseStatus;
} 
@mrjcleaver
Copy link
Owner Author

I am not going to update the invoice and baseinvoice for now, because I don't use them. YMMV.

@mrjcleaver
Copy link
Owner Author

An approach could to convert routines like:

FreshBooks_Element.prototype.create = function(){
    var content = "";
    content = this.internalPrepareCreate(content);
    var responseXML = this.sendRequest(content,"create");
    var responseStatus = this.processResponse(responseXML);
    this.internalCreate(responseStatus,responseXML);
return responseStatus;
 }

Into something like:

FreshBooks_Element.prototype.create = function(){
    var content = "";
    content = this.internalPrepareCreate(content);
    this.sendRequest(content,"create", this.createCallback);
}

FreshBooks_Element.prototype.createCallback = function(responseXML){
    var responseStatus = this.processResponse(responseXML);
    this.internalCreate(responseStatus,responseXML);
    return responseStatus;
}

And then do the same for the other methods.

@mrjcleaver
Copy link
Owner Author

Further, I suppose - for non-node invocations - traditional JS, in sendRequest, I could call the callback immediately.

From:

this.browserSend = function (content) {
    var url = "";
    if (this.proxyUrl) {
        url = this.proxyUrl + "?url=" + this.url;
    } else {
        url = this.url;
    }
    var post = "token=" + this.token + "&content=" + content;
    this.request.open("POST", url, false, this.token);
    this.request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    this.request.setRequestHeader("Content-length", post.length);
    this.request.send(post);
    if (this.request.status == 200) {
        return this.request.responseXML;
    } else {
        return false;
    }
};

To:

this.browserSend = function (content, callback) {
    var url = "";
    if (this.proxyUrl) {
        url = this.proxyUrl + "?url=" + this.url;
    } else {
        url = this.url;
    }
    var post = "token=" + this.token + "&content=" + content;
    this.request.open("POST", url, false, this.token);
    this.request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    this.request.setRequestHeader("Content-length", post.length);
    this.request.send(post);
    if (this.request.status == 200) {
        callback(this.request.responseXML);
    } else {
        callback(false);
    }
};

@mrjcleaver
Copy link
Owner Author

I ended up converting:

 FreshBooks_Element.prototype.create = function(){
    var content = "";
    content = this.internalPrepareCreate(content);
    var responseXML = this.sendRequest(content,"create");
    var responseStatus = this.processResponse(responseXML);
    this.internalCreate(responseStatus,responseXML);
    return responseStatus;
 }

Into:

FreshBooks_Element.prototype.create = function(callbackParameter){
    var self = this;     // closure to remember who I am inside the callback

    var callback = function(responseXML){
        var responseStatus = self.processResponse(responseXML);
        self.internalCreate(responseStatus, responseXML);
        if (callbackParameter) {   // Is there a better way, e.g. convention, than passing self like this?
            callbackParameter(responseStatus, self);
        }
    }

    var content = "";
    content = this.internalPrepareCreate(content);
    this.sendRequest(content, "create", callback);
}

Where HttpClient.send is aliased to nodeSend if running under Node:

this.nodeSend = function (content, parameterCallback) {
    if (!content) {
        console.trace();
        throw("No content!");
    } else {
        logger.info("content "+content);
    }

    if (parameterCallback) {
        responseCallback = parameterCallback;
    } else {
        responseCallback = processNodeResponse;
    }

    logger.info("Response will go to ", responseCallback);

    var responseCallback = function (error, response, bodyXML) {
        logger.info("Callback on Response: ", error, bodyXML); // don't print response object
        var xmlObject;

        if (bodyXML) {
            if (typeof bodyXML === "string") {                // in Node, this is a string.
                                                              // the js library gets this as an XMLObject
                xmlObject = jsdom.jsdom(bodyXML);
                logger.info("Converted ",bodyXML," to XML");
            } else {
                xmlObject = bodyXML;
            }
            parameterCallback(xmlObject);
        } else {
            console.trace("No callback, no one to notify");
        }
    };

    var errorCallback = function (error, response, body) {
        logger.info('ERROR =========================== ' + error + ' ' + response + ' ' + body);
    };

    options = {
        url:this.url,
        method:"POST",
        headers:{
            contentType:"text/xml",
            'Content-Length':content.length
        },
        body:content || "",
        onError:errorCallback
    };

    logger.info("Node Send:", options);
    req = nodeRequest(options, responseCallback);// ('callback', error, response, body)
    logger.info("moving on");

};

And:

FreshBooks_Element.prototype.sendRequest = function(content, methodName, callback){
    this.lastError = "";
    var requestXML = this.requestEnvelope(content,methodName);
    GLOBAL.httpClient.send(requestXML, callback);           //what was a return value is now passed into the callback
}

@mrjcleaver
Copy link
Owner Author

What's left to do now is convert all the other IO methods to use callbacks - question is - did I do this reasonably well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant