简体   繁体   中英

Javascript smooth method chaining

I have a webservice which can take certain parameters, like such, ?top=5&orderby=column --- etc...

I want to be able to execute my object like such:

var call = new API();
call.get().top(5).skip(15).orderby("address");

The challenge is to only have the last orderby trigger the execute() method. Here is my code. Please let me know if you have any better ideas! It's currently delaying by 25ms when each function ends, and stops the timer when the next function starts. Is this proper/acceptable?


var API = function (webservice) {
this.webservice(webservice);
return this;
};

API.prototype = {
version: function (urlFormat) {
    if (urlFormat) {
        return "v" + urlFormat.split('.').join('_');
    }
    return sessionStorage.getItem("version");
},
path: function () {
    return "../WebAPI/";
},
execute: function () {
    var path = this.path() + this.webservice() + ".svc/";
    if (this.__parameters) {
        path += "?";
    }
    var first = true;
    for (var k in this.__parameters) {
        if (k !== "type")
        path += ((first) ? (function(){first = false; return ""})() : "&") + "$" + k + "=" + this.__parameters[k];
    };
    console.log(this.__parameters.type + ": " + path);
    return this;
},
put: function () {
    this.doIt("type","put");
    return this;
},
post: function () {
    this.doIt("type","post");
    return this;
},
get: function() {
    this.doIt("type","get");
    return this;
},
delete: function() {
    this.doIt("type","delete");
    return this;
},
toString: function () {
    return "API";
},
webservice: function(webservice) {
    if (webservice) {
        this.__webservice = webservice;
    }
    else {
        return this.__webservice;
    }
},
top: function (p) {
    this.doIt("top",p);
    return this;
},
view: function (p) {
    this.doIt("view",p);
    return this;
},
orderby: function (p) {
    this.doIt("orderby",p);
    return this;
},
criteria: function (p) {
    this.doIt("criteria",p);
    return this;
},
skip: function (p) {
    this.doIt("skip",p);
    return this;
},
filter: function (p) {
    this.doIt("filter",p);
    return this;
},
doIt: function (method, parameter) {
    this.__timerStop();
    this.__parameters[method] = parameter;
    this.__timerStart();
},
__timerStop: function () {
    if (this.__timer) {
        clearTimeout(this.__timer);
    }
},
__timerStart: function (append) {
    var self = this;
    if (this.__timer) {
        this.__timerStop();
    }
    this.__timer = setTimeout(function() {
        console.log("executing.");
        console.log(JSON.stringify(self.__parameters));
        self.execute();
    }, 25);
},
__parameters: {}
};

Update : You know what? I'm going to soften my stance on this one (original answer below). You should actually be OK given that the callback you're passing to setTimeout can never fire before your method chain is "complete" given JavaScript's single-threaded event loop. (And in fact, this also implies you should be safe passing 0 to setTimeout instead of 25 .)

I still think you're nuts for thinking up this design (and if this is code that multiple developers will be touching, I'd say you're better off with a simpler design just to lessen the risk of team confusion from undue complexity); but if you insist on taking this path, you actually shouldn't run into any weird Heisenbugs.

But yeah, I stand by my original advice about requiring the execute call explicitly.


Oh man. You are crazy to even be considering this! I mean, part of me does love you for it (I am a big fan of horrifying hacks ); but the fact is that taking this approach, while it might end up working, will drive you nuts if/when it goes haywire.

The main reason I would strongly discourage it is that the alternative is very easy and, more importantly, actually reliable : just establish the rule that execute is the method that actually sends the request, and so any chained method call must end with that:

call.get().top(5).skip(15).orderby("address").execute();

If you're seriously in love with this timer-based idea, something tells me you've never really suffered from a Heisenbug before (or, as I originally guessed, you're just out of your mind).

Interesting idea. Although, why not do something like this instead:

call({ type: "get", top: 5, skip: 15, orderby: "address" });

Then process each argument by looping through the object inside your call implementation, then make the service request.

for(var arg in args) {
    if(args.hasOwnProperty(arg) && args.propertyIsEnumerable(arg)) {
        // process argument
    }
}

This keeps things simple.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM