Converting a portion of a script to callback with promise - javascript

I need to do some API call and also DB manipulation during rendering of messages for a function.
I have these long function, let's call it alongFunction
function longFunction(message, item) {
var win = lib.gui.window.get(id);
var msg = message.msg;
var id = message.id;
var direction = message.direction;
//inside are basically some DOM manipulation
//around 10 lines here
//and also there are rendering/misc code
//around 10 lines here
//then we are rendering some messages here:
var msgDiv = $('<div>'), msgTsDiv = $('<div>');
msgDiv.addClass('chat '+direction);
msgDiv.attr('id', id);
if(direction == 'in') {
//I need to add a hook here to do API call, but it takes some time
msgDiv.html('<div class="message in">'+message+'</div>');
} else {
msgDiv.html('<div class="message out">'+message+'</div>');
}
//NEW CODE ADDED HERE: Check below pls
//and because JS is async, it just passes these codes
//around 50 codes for manipulation etc...
if(message.isReceived()) {
msgDiv.addClass('received');
}
//and some other stuff here...
}
I added at the NEW CODED ADDED HERE these:
function longFunction(message, item) {
var win = lib.gui.window.get(id);
var msg = message.msg;
var id = message.id;
var direction = message.direction;
//inside are basically some DOM manipulation
//around 10 lines here
//and also there are rendering/misc code
//around 10 lines here
//then we are rendering some messages here:
var msgDiv = $('<div>'), msgTsDiv = $('<div>');
msgDiv.addClass('chat');
msgDiv.attr('id', id);
//only translating message coming in
if(direction == 'in') {
getTranslatedMessage(message).then(function(newMessage){
//I need to add a hook here to do API call, but it takes some time
msgDiv.html('<div class="message in">'+newMessage+'</div>');
}).catch(function(error){
if(error) {
msgDiv.html('<div class="message">'+message+'<br>'+error+'</div>');
} else {
msgDiv.html('<div class="message in">'+message+'</div>');
}
});
} else {
msgDiv.html('<div class="message out">'+message+'</div>');
}
//end of translation - UPDATE
//and because JS is async, it just pass these codes
//around 50 codes for manipulation etc...
if(message.isReceived()) {
msgDiv.addClass('received');
}
//and some other stuff here...
}
UPDATE:
I have tried
putting everything into one big callback = but it is not returning any result.
repeat the remaining code by copy-pasting it into the callback and outside = the message printed is not in proper sequence (this function is called in a loop)
Note: The function is a bit long, so I just made a simple version for it
Important: This function is being called in a loop.
Thank you in advance!

Related

Next iteration of $.each when received AJAX-content

The question has been asked before, but it is almost four years ago and maybe there is a better solution.
I have a $.each-loop where sometimes additional data is being fetched via ajax.
I am bulding an object with the fetched data, after the loop there is a function that generates HTML from the object. The problem is that the loop finishes before the ajax data arrives. If I place an alert in the HTML-generating-function the content is loading properly.
I am searching for a solution that calls the HTML-generator-function only when the loop and all ajax calls are finished. Maybe it is a solution to count the started Ajax requests and wait if all of them are finished?
I believe jQuery deferred is the right solution for me but I do find only examples where everything stays inside the loop. Can someone help?
I have stripped down my code to the most important things:
//goes through each testplace -->main loop
$.each(jsobject, function(key, value)
{
//build object together...
for (var i = 0, numComputer = jenkinsComputer.contents.computer.length; i < numComputer; i++)
{
//If the testplace is in both objects then fire AJAX request
if (jenkinsComputer.contents.computer[i].displayName == key) //<<<This can happen only once per $.each loop, but it does not happen every time
{
//next $.each-iteration should only happen when received the JSON
var testplaceurl = jenkinsComputer.contents.computer[i].executors[0].currentExecutable.url;
$.when($.getJSON("php/ba-simple-proxy.php?url=" + encodeURI(testplaceurl) + "api/json?depth=1&pretty=1")).done(function(jenkinsUser)
{
//build object together...
});
}
}
}); //End of main Loop ($.each)
generateHTML(builtObject);
It would be great if someone could give me an advice how to do it.
I would do something like this:
var thingstodo = $(jsobject).length;
var notfired = true;
$.each(jsobject, function(key, value)
{
//build object together...
for (var i = 0, numComputer = jenkinsComputer.contents.computer.length; i < numComputer; i++)
{
//If the testplace is in both objects then fire AJAX request
if (jenkinsComputer.contents.computer[i].displayName == key) //<<<This can happen only once per $.each loop, but it does not happen every time
{
//next $.each-iteration should only happen when received the JSON
var testplaceurl = jenkinsComputer.contents.computer[i].executors[0].currentExecutable.url;
$.when($.getJSON("php/ba-simple-proxy.php?url=" + encodeURI(testplaceurl) + "api/json?depth=1&pretty=1")).done(function(jenkinsUser)
{
//build object together...
thingstodo--;
if(thingstodo === 0 && notfired){
notfired = false;
generateHTML(buildObject);
}
});
}else{
thingstodo--;
}
}
}); //End of main Loop ($.each)
if(thingstodo === 0 && notfired){
generateHTML(buildObject);
}
This is short untested example about the solution. I hope this to give you idea.
// I guess that jsobject is array ..
// if it is not object you can use something like:
// var keys = Object.getOwnPropertyNames(jsobject)
(function () {
var dfd = $.Deferred();
function is_not_finished() {
return jsobject.length > 0 && jenkinsComputer.contents.computer.length > 0;
}
(function _handleObject() {
var key = jsobject.shift();
var displayName = jenkinsComputer.contents.computer.shift().displayName;
if (displayName == key) //<<<This can happen only once per $.each loop, but it does not happen every time
{
//next $.each-iteration should only happen when received the JSON
var testplaceurl = jenkinsComputer.contents.computer[i].executors[0].currentExecutable.url;
$.getJSON("php/ba-simple-proxy.php?url=" + encodeURI(testplaceurl) + "api/json?depth=1&pretty=1").done(function(jenkinsUser)
{
//build object together...
if(is_not_finished()) {
setTimeout(_handleObject,0);
} else {
dfd.resolve();
}
});
} else if (is_not_finished()) {
setTimeout(_handleObject,0);
} else {
dfd.resolve();
}
}());
return dfd.promise();
}()).done(function () {
generateHTML(builtObject);
});

Processing.js Threading

I have a function in my processing.js file that calls for an AJAX script to return an array with which I want to define some simple starting variables for my code. I want this function to complete before any other part of the code that comes after it is executed.
When i run my code both the alerts in the setup() phase and the first alert in the draw() return "undefined" while the next iterations in the draw() phase return the actual array I need.
The processing code:
var loadArray;
setup() {
loadArray = new Array();
js_GetArray(function(data) {
loadArray = data;
});
alert(loadArray[1]);
alert(loadArray[2]);
}
void draw()
{
alert(loadArray[k]);
k++;
}
The AJAX:
<!-- Read the vars numbers from the mysql database and return them -->
<script language="JavaScript" type="text/javascript">
function js_GetArray(callback) {
$.get( "load.php", function( actiondata ) {
var obj = JSON.parse(actiondata);
callback(obj);
});
}
</script>
Is there a way to force the program to complete the js_GetArray call before moving on to the rest of the code?
If it is during loading of the page, then what about using a flag as indication that the call has returned?
var loadArray;
var isloaded = false;
setup() {
loadArray = new Array();
js_GetArray(function(data) {
loadArray = data;
isloaded = true;
alert(loadArray[1]);
alert(loadArray[2]);
});
}
void draw()
{
if(isloaded)
{
alert(loadArray[k]);
k++;
}
}
EDIT:
I have moved the alerts inside the callback function.
As an alternative answer to the one given, I've covered this on the processingjs.org website extensively, so you probably want to give that a read: http://processingjs.org/articles/PomaxGuide.html#interface

node.js wait for a callback/response if i use process.send()

I got a main process and this fork a child. This child do some calculations. At some point of the code in the child I want to ask the parent for some data. This request is highly dynamic and at this point i want to wait for a answer/response of this request. But how do I wait for process.send() or is it possible to add a Callback function to .send()?
I tried to break down my Problem to a simple example.
The highly dynamic value is in my example the randomval in the worker.
And i know that the assignment
var c = process.send({msg:'get_c',randomval:Math.floor((Math.random()*10)+1)});
can't work. But i no other idea how to describe the Problem.
main.js
var childProcessCalc = require('child_process').fork(__dirname + '/worker');
childProcessCalc.send({msgtype:'docalc'});
childProcessCalc.on('message',function(msg){
if(msg.msg === 'pi')
{
console.log("Pi"+msg.pi+" by c="+msg.c);
}
else if(msg.msg === 'get_c')
{
console.log('child wants c');
childProcessCalc.send({msgtype:'your_c',c:1000000*msg.randomval});
}
});
childProcessCalc.on('exit',function(){
console.log('main:the childProzess has exit!')
});
worker.js
process.on('message', function(msg){
if(msg.msgtype == 'docalc') {
//Here is my Problem, how to wait for the message thats the response for
//exactly this send / randomval, or how to add a callback to send
var c = process.send({msg:'get_c',randomval:Math.floor((Math.random()*10)+1)});
var Pi=0;
var n=1;
for (var i=0;i<=c;i++)
{
Pi=Pi+(4/n)-(4/(n+2))
n=n+4
}
process.send({msg:'pi',pi:Pi,c:c})
}
else if(msg.msgtype === 'your_c')
{
console.log('parent hase sendc='+msg.c);
}
});
I have a solution to my problem and it works well for me, but because im very new at nodejs i still not now if this is the best way. Its feel like a overhead.
In a few words what i have done:
I added a object that stores a random callback identifier with the callback function that has to be called if we got a response for the given callback identifier.
When i now call send() from worker i send the identifier to the main process and the main process send this identifier back when he has finished. So i can lookup in my callback var (dynamicMassages ) for the callbackfnc to call and execute it.
main2.js
var childProcessCalc = require('child_process').fork(__dirname + '/worker2');
childProcessCalc.send({msgtype:'docalc'});
childProcessCalc.send({msgtype:'docalc'});
childProcessCalc.on('message',function(msg){
if(msg.msg === 'pi')
{
console.log("Pi"+msg.pi+" by c="+msg.c);
}
else if(msg.msg === 'get_c')
{
console.log('child wants c');
childProcessCalc.send({msgtype:'your_c',callbackid:msg.callbackid, c:1000000*msg.randomval});
}
});
childProcessCalc.on('exit',function(){
console.log('main:the childProzess has exit!')
});
worker2.js
var dynamicMassages = {};
process.on('message', function(msg){
var getRandomId = function(){
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ )
{
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
if(dynamicMassages[text] === undefined)
{
return text;
}
else
{
return getRandomId();
}
};
if(msg.msgtype == 'docalc') {
var randomId = getRandomId();
var callbackFnc = function(c){
var Pi=0;
var n=1;
for (var i=0;i<=c;i++)
{
Pi=Pi+(4/n)-(4/(n+2))
n=n+4
}
console.log("callbackFnc For:"+randomId);
process.send({msg:'pi',pi:Pi,c:c})
delete dynamicMassages[randomId];
};
dynamicMassages[randomId] = callbackFnc;//callbackFnc;
process.send({msg:'get_c',callbackid: randomId, randomval:Math.floor((Math.random()*10)+1)});
}
else if(msg.msgtype === 'your_c')
{
console.log('parent hase sendc='+msg.c+' for callbackId '+msg.callbackid);
if(msg.callbackid !== undefined)
{
dynamicMassages[msg.callbackid](msg.c);
}
}
});
Please leave a comment if you would to it the same way.
I'd suggest that you go with a message bus, either a full-blown advanced solution such as RabbitMQ, or a little smaller solution, such as axon.
Basically, what you want to do is inter-process communication, and I'd try to stick to established protocols and standards as much as possible, and avoid rolling your very own solution. As RabbitMQ builds on top of AMQP, I guess you can call it standard.

Put data from a IndexedDB data base in a variable

I'm trying to put the data read from the database in a variable. I have tryed many things, including a callback function, but nothing looks to work when the process is outside of the "opencursor().onsuccess" function scope.
The "Alert 1" show the result correctly, but "Alert 2" and "Alert 3" don't.
I'm calling the 'main()' function from HTML code.
I'm really frustrated, because I have been mining internet searching for the solution without any positive result.
Can anybody help me, please?
Thanks a lot.
var data=[];
function kkeyget(t1, db_name, db_version)
{
var request = indexedDB.open(db_name, db_version);
request.onerror=function()
{
alert("Error");
}
request.onsuccess=function(event)
{
var db=this.result;
var trans = db.transaction(t1, "readonly");
var objectStore = trans.objectStore(t1);
objectStore.openCursor().onsuccess = function(event)
{
var cursor = event.target.result;
if (cursor)
{
data.push(cursor.value);
//Alert 1:
alert(data[0].id);
cursor.continue();
}
else alert("No more entries!");
};
}
//Alert 2:
alert(data[0].id);
}
function main()
{
kkeyget("agenda", "example_db", 1);
//Alert 3:
alert(data[0].id);
}
Correct. Because all indexedDB actions are asynchronous, your code will run:
alert 2 // undefined
alert 3 // undefined
alert 1 // correct
In order to get this closer to a synchronous action, you need to have it call a new function after it's done collecting data. Where your alert("No more entries!") is.
Instead of trying to return a key, pass in a custom callback function that takes the retrieved key as its argument.
// Your old function slightly modified
function kkeyget(t1, db_name, db_version, callback, fallback) {
// ... yada yada yada
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
callback(cursor.value);
} else {
fallback();
}
}
}
// Now in your calling code:
kkeyget('a','b',1, function(id) {
// Do something with the 'id'
// here in this anonymous function callback
}, function() {
// Do something here to indicate no match
});

XMLHttpRequest - when returning responseText to another function, it returns undefined

SOLVED.
I changed my code into this:
function init() {
//preloadImages();
getContent('events', 'events');
getContent('content', 'main');
}
function loadingScreen(start) {
var loadingSpan = document.getElementById('loading');
if (start == true) {
loadingSpan.innerHTML = '<p>Loading...<br><img src="images/loading.gif"></p>';
}
else {
loadingSpan.innerHTML = '';
}
}
function getContent(what, where) {
if (what == 'content') {
loadingScreen(true);
var ranLoad = true;
}
var toSet = document.getElementById(what);
var location = "content/" + where + ".txt";
var request = new XMLHttpRequest;
request.open("GET", location, true);
request.onreadystatechange = function(){
if (request.readyState == 4 && request.status == 200){
toSet.innerHTML = request.responseText;
if (ranLoad==true){
loadingScreen(false);
}
}
}
request.send(null);
}
window.onload = init;
tl;dr or long-winded - see the code and the results below.
So. I am building a small webpage for a friend, and I decided to try out a technique where instead of writing the content directly in the webpage, I will use XMLHttpRequest to retrieve content (in the same domain), and place them in the content , where it will be updated by javascript, when people click on a different anchor.
Well, I came across a roadbump.
When I created the functions for getting the content (setEvents and setContent), where it creates a variable and calls a function for setting the variable (getMarkup), when the function was called, and the return statement was executed, it returns undefined. I found a thread similar, but their solution was to add the innerHTML statement DIRECTLY in the getMarkup function. I don't want to do that.
Here's the code and the results:
Edit: Esailija suggested that I should have just posted the code. To me it was a tad bit easier to just take the image, but here it is:
function init() {
//preloadImages();
setEvents();
setContent('main');
}
function setEvents() {
var eventDiv = document.getElementById("events");
var eventContent = getMarkup("content/events.txt");
eventDiv.innerHTML = eventContent;
}
function setContent(which) {
loadingScreen('start');
var contentDiv = document.getElementById('content');
location_ = "content/" + which + 'txt';
//var contentContent = getMarkup('location');
//contentDiv.innerHTML = contentContent;
loadingScreen('stop');
}
function loadingScreen(action) {
var loadingSpan = document.getElementById('loading');
loadingSpan.innerHTML = "Test";
if (action == 'start') {
loadingSpan.innerHTML = '<p>Loading...<br><img src="images/loading.gif"></p>';
}
if (action == 'stop') {
loadingSpan.innerHTML = '';
}
}
function getMarkup(where) {
var filerequest = new XMLHttpRequest();
filerequest.open("GET", where, true);
filerequest.onreadystatechange = function() {
if (filerequest.readyState == 4 && filerequest.status == 200) {
var test = document.getElementById("events");
var reply = filerequest.responseText;
//Doesn't work
return reply;
//Works just fine
//test.innerHTML = reply;
}
};
filerequest.send(null);
}
window.onload = init;
When I do the innerHTML instead of return it shows "Test TEST", and when I do return instead of innerHTML, it shown "undefined".
I really don't want to do the innerHTML part of it, so is there a workaround to make that return statement work?
Are you familiar with callbacks? Open up Firebug and put a breakpoint on your return reply; line - notice the call stack. The function where you have return reply; is not getMarkup.
Basically you're going to have to restructure your code a little. I would have getMarkup take an additional parameter - the DOM element to which you want to set its innerHTML value.
It seems that you don't quite understand how ajax works.
Once you call your getMarkup function, you send an ajax request and tell the browser to use the function after onreadystatechange to handle the reply. So actually when the reply is back, it's not your other functions like setContent that are calling that handler but the browser. That's why the return doesn't work.
Also when you call something like var contentContent = getMarkup('location'); , as getMarkup has no explicit return value, so by default contentContent gets undefined. You may think that it should get the return value inside your anonymous function but that's not the case. The getMarkup function returns immediately but the handler will be called only when the ajax response comes.
If you want to make that nice, you will have to do something extra like: you only have one ajax handler like you did. Once a function called that ajax function it register its callback into a queue and when the response is back the queue is popped and the values are then updated. This would take some time for you to build this mechanism or you may need to check how the jQuery Ajax Queue works.

Categories

Resources