3

I'm new to node.js, so before releasing my node.js app, I need to be sure it will work as it should.

Let's say I have an array variable and I intialize it on beginning of my script

myArray = [];

then I pull some data from an external API, store it inside myArray, and use setInterval() method to pull this data again each 30 minutes:

pullData();
setInterval(pullData, 30*60*1000);

pullData() function takes about 2-3 seconds to finish.

Clients will be able to get myArray using this function:

http.createServer(function(request, response){
var path = url.parse(request.url).pathname;
if(path=="/getdata"){

    var string = JSON.stringify(myArray);
    response.writeHead(200, {'Content-Type': 'text/plain'});
    response.end(string);              

}
}).listen(8001);

So what I'm asking is, can next situation happen?: An client tries to get data from this node.js server, and in that same moment, data is being written into myArray by pullData() function, resulting in invalid data being sent to client?

I read some documentation, and what I realized is that when pullData() is running, createServer() will not respond to clients until pullData() finishes its job? I'm really not good at understanding concurrent programming, so I need your confirmation on this, or if you have some better solution?

EDIT: here is the code of my pullData() function:

 var now = new Date();

Date.prototype.addDays = function(days){

        var dat = new Date(this.valueOf());
        dat.setDate(dat.getDate() + days);
        return dat;
}


var endDateTime = now.addDays(noOfDays);
var formattedEnd = endDateTime.toISOString(); 

var url = "https://api.mindbodyonline.com/0_5/ClassService.asmx?wsdl";
    soap.createClient(url, function (err, client) {
        if (err) {
            throw err;
        }

        client.setEndpoint('https://api.mindbodyonline.com/0_5/ClassService.asmx');
        var params = {
            "Request": {
                "SourceCredentials": {
                    "SourceName": sourceName,
                    "Password": password,
                    "SiteIDs": {
                        "int": [siteIDs]
                    }
                },
                "EndDateTime" : formattedEnd

            }
        };


client.Class_x0020_Service.Class_x0020_ServiceSoap.GetClasses(params, function (errs, result) {
            if (errs) {
                console.log(errs);
            } else {

                    var classes = result.GetClassesResult.Classes.Class;
                    myArray = [];

                    for (var i = 0; i < classes.length; i++) {
                        var name = classes[i].ClassDescription.Name;
                        var staff = classes[i].Staff.Name;
                        var locationName = classes[i].Location.Name;
                        var start = classes[i].StartDateTime.toISOString();
                        var end = classes[i].EndDateTime.toISOString();
                        var klasa = new Klasa(name,staff,locationName,start,end);

                        myArray.push(klasa);
                    }

                    myArray.sort(function(a,b){
                        var c = new Date(a.start);
                        var d = new Date(b.start);
                        return c-d;
                    });

                    string = JSON.stringify(myArray);
     }
        })


    });
kecman
  • 813
  • 3
  • 14
  • 34
  • 1
    Why don't you use a database to store data? Cache it if you need performance. Also, if you start scaling with multiple machines each machine will end up with a different `myArray` and the response that the client get will depend on the machine that receives the request! This will work just fine for a small server and the data will be stored till you restart the server! – rohithpr Dec 20 '15 at 16:03
  • It's not problem like this, there is not much of data to be stored, and on each start of server data is pulled again, so no problem to lose data when server is restarted. I wouldn't complicate it with database. Thanks for your response anyway :) – kecman Dec 20 '15 at 16:14

4 Answers4

3

No, NodeJs is not multi-threaded and everything run on a single thread, this means except non-blocking calls (ie. IO) everything else will engage CPU until it returns, and NodeJS absolutely doesn't return half-way populated array to the end user, as long as you only do one HTTP call to populate your array.

Update: As pointed out by @RyanWilcox any asynchronous (non-blocking syscall) call may hint NodeJS interpreter to leave your function execution half way and return to it later.

Boynux
  • 5,958
  • 2
  • 21
  • 29
  • 1
    "as long as you only do one HTTP call to populate your array. " <-- a very important point... although it's not just HTTP calls, but *any asynchronous operation*. – RyanWilcox Dec 20 '15 at 16:13
  • @Boynux please look at my pullData() function, I just added it in first post, and see if this will work OK? – kecman Dec 20 '15 at 16:46
  • Difficult to answer, if I assume that `GetClasses` method from soap client does not do any async call and just calls your callback with data, yes, that's safe. – Boynux Dec 20 '15 at 16:56
  • GetClasses is not soap client method, it is API's SOAP endpoint. So it should be safe then? – kecman Dec 20 '15 at 17:00
  • Right, it's a proxy for your SOAP class. My best guess is yes, it's safe. This is from my understanding of SOAP. But yet I'm not sure about JS SOAP client implementation. That's why it's just a guess :) – Boynux Dec 20 '15 at 17:04
2

In general: No.

JavaScript is single threaded. While one function is running, no other function can be.

The exception is if you have delays between functions that access the value of an array.

e.g.

var index = i;
function getNext() {
    async.get(myArray[i], function () {
        i++;
        if (i < myArray.length) {
            getNext()
        }
    });
}

… in which case the array could be updated between the calls to the asynchronous function.

You can mitigate that by creating a deep copy of the array when you start the first async operation.

Community
  • 1
  • 1
Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • Thanks for your extensive answer. My pullData function updates myArray only once in each 30 minutes, so this should be okay ;) – kecman Dec 20 '15 at 16:22
2

Javascript is single threaded language so you don't have to be worried about this kind of concurrency. That means no two parts of code are executed at the same time. Unlike many other programming languages, javascript has different concurrency model based on event loop. To achieve best performance, you should use non-blocking operations handled by callback functions, promises or events. I suppose that your external API provides some asynchronous i/o functions what is well suited for node.js.

madox2
  • 49,493
  • 17
  • 99
  • 99
1

If your pullData call doesn't take too long, another solution is to cache the data.

Fetch the data only when needed (so when the client accesses /getdata). If it is fetched you can cache the data with a timestamp. If the /getdata is called again, check if the cached data is older than 30 minutes, if so fetch again.

Also parsing the array to json..

var string = JSON.stringify(myArray);

..might be done outside the /getdata call, so this does not have to be done for each client visiting /getdata. Might make it slightly quicker.

peerbolte
  • 1,169
  • 11
  • 19
  • Fetching the data only when needed is not an option, because of limit to numer of API calls, it must be once in 30 minutes, and the freshness of pulled data is not that important, 30 minutes is ok. And thanks for advice about moving stringify() line of code out of /getdata call, it should speed responses a little. – kecman Dec 20 '15 at 16:24
  • Maybe my answer was not clear enough. You will not fetch more often than every 30 minutes if you use caching right. It wil only fetch again if the cached data is older than 30 minutes. In fact, if there is only one visitor in two hours, you will have only one call to the api instead of four (four callls are needed when you use an interval of 30 minutes). – peerbolte Dec 21 '15 at 16:58