IndexedDB - Can't pass variable after onsuccess when opening database - javascript

I have been looking for assistance with setting up an IndexedDB for web storage, and I have run into a problem that I cannot find a good answer to. After I have successfully setup/opened the database I am having trouble passing the variable that contains the database information to access it later. Here is my code, I have been following a guide from MDN :
const DB_NAME = 'database-name';
const DB_VERSION = 2;
const DB_STORE_NAME = 'users';
var db;
function openDb() {
console.log('openDb ...')
var request = indexedDB.open(DB_NAME, DB_VERSION);
request.onsuccess = function(event) {
db = request.result;
console.log("openDb DONE");
};
request.onerror = function(event) {
console.log(request.errorCode);
};
request.onupgradeneeded = function(event) {
console.log('openDb.onupgradeneeded');
var store = event.currentTarget.result.createObjectStore(DB_STORE_NAME, { keyPath: 'id', autoIncrement: true });
store.createIndex('age', 'age', { unique: false });
};
}
function getObjectStore(store_name, mode) {
var tx = db.transaction(store_name, mode);
return tx.objectStore(store_name);
}
When getObjectStore is called the variable db is undefined. My knowledge of javascript is very limited and some concepts I don't get. The guide doesn't not show anything special being done and their demo works as is. Some other guides have mentioned implementing a callback, but they don't show how its done nor do I understand the concept of callbacks. Any help is appreciated. Thanks.

Unfortunately you need to learn about a relatively complicated concept usually referred to as asynchronous JavaScript before proceeding to use indexedDB. There are already several thousand questions related to AJAX on stackoverflow. I am trying to think of the most polite way to say this, but basically, the answer you are looking for is already provided by these other questions, and by many other websites. Nevertheless, here are some quick tips.
First, your approach is never going to work. You cannot skip over learning about async.
Second, do not use the setTimeout trick to get it to work. That is horrible advice.
Third, at a general level, a callback is simply a word used to describe a function when the function is used in a particular way. Specifically, a callback refers to a function that is passed as an argument to another function, where the other function then maybe calls the function at some later point in time. More specifically, a callback is generally a function that is called at the end of the function it is passed to, when the function has completed.
For example:
function a(b) { alert(b); }
function c(d) { d('hi'); }
c(a);
That might look a bit confusing at first but it is the simplest thing I can describe off the top of my head. In the example, the final line calls function c and passes in function a. The effect of the code is that you see 'hi' as a browser alert. In this example, the function a is passed as a parameter/argument to function c. Function c uses the name d for its one and only argument. c calls d with the string 'hi'. When describing this example, we would say that argument d represents a callback function passed to function c. We could also say that function a is the particular callback function used by function c. So that is basically it. When you pass a function in as an argument and the other function calls the passed in argument, you are using a callback.
Then things gets way more complicated, because you have to learn about how to read and write asynchronous code. Properly introducing it would take several pages. Here is an extreme crash course.
You have traditionally been writing synchronous code, even if you did not call it that. Synchronous code runs exactly when you expect it to, in the order that you write your statements. Here is a brief example of typical sync code:
function sum(a, b) { return a + b; }
alert(sum(1, 2));
Simple stuff. The next example is code that uses a callback, but is still synchronous.
function doOperation(op, num1, num2) { return op(num1, num2); }
function sumOperation(num1, num2) { return num1 + num2; }
var result = doOperation(sumOperation, 1, 2);
alert(result);
Here we passed the sumOperation function into the doOperation function. sumOperation is the callback function. It is the first argument with the name 'op'. Still pretty simple stuff. Now consider the next example. The point of the next example is to show how we pass control to the function to do something. Kind of like how goto/labels work.
function doOperation(op, num1, num2) {
var result = op(num1, num2);
alert(result);
return undefined;
}
function sumOperation(num1, num2) { return num1 + num2; }
doOperation(sumOperation, 1, 2);
Notice how doOperation no longer returns a value. It has the logic within its function body. So once we call doOp, the browser starts running the code inside doOperation. So we switched from the outer context into the body of the function. Also, because doOperation doesn't return anything, we cannot do anything with its return value. The logic is locked inside the body of the doOperation function. The code still works about the same, its just that now we are not returning anything from doOperation, and now the logic is inside doOperation instead of outside in the main/global context.
Now an example that uses setTimeout. This is completely unrelated to the suggestion of using setTimeout.
function doOperation(op, num1, num2) {
setTimeout(function runLater() {
var result = op(num1, num2);
alert(result);
return undefined;
}, 1000);
return undefined;
}
function sumOperation(num1, num2) { return num1 + num2; }
doOperation(sumOperation, 1, 2);
The point here is to understand that we use a callback (named runLater in this example), and that the code inside the callback does not run immediately. Therefore, we can no longer say it runs synchronously. We instead refer to the statements constituting the body of the callback function as asynchronous. So now an alert appears after 1 second. Notice how we cannot return anything from runLater. Also notice how we cannot return anything from doOperation. There is nothing to return. There is no way to get the value in the 'result' variable out of the scope of runLater. It is locked in there.
Let's try almost the same thing, but try to have runLater set a variable. Also, I am going to omit 'return undefined' because that is what every function without a explicit return statement returns.
var aGlobalResult = null;
function doOperation(op, num1, num2) {
setTimeout(function runLater() {
aGlobalResult = op(num1, num2);
}, 1000);
}
function sumOperation(num1, num2) { return num1 + num2; }
doOperation(sumOperation, 1, 2);
alert(aGlobalResult);
Hopefully you are catching on to the problem. First off, runLater does not return anything, so doOperation does not return anything, so we could not even try to do something like aGlobalResult = doOperation(...);, because that would not make any sense. Second, the result here is that you will see an alert 'undefined' because the alert statement executes prior to the statement that assigns a value to aGlobalResult. This is even though you wrote the assignment statement higher up (earlier) in the code, and the alert is later. This is the brick wall some newer developers run into right here. This is indeed confusing for some. aGlobalResult is undefined here because setTimeout does not set it until later. Even if we passed in 0 milliseconds to setTimeout, it is still 'later', meaning that the assignment happens at a later point in time, after the alert. The alert message is always going to be undefined. There is absolutely nothing you can do to avoid the way this works. Nothing. Period. Stop trying. Learn it, or give up entirely.
So, how does one typically write code that behaves or involves asynchronous stuff? By using callbacks. Which again means that you can no longer use return statements to assign values to outer scope variables. You instead want to write functions and pass around control to various functions. In other words, instead of:
function a() {}
function b() {}
function c() {}
a(); b(); c();
you write code like this:
function a(callback) {
var asdf = 1+2; // do some stuff in a
alert('a finished');
// a has now completed, call its callback function, appropriately named callback
callback();
}
function b(callback) {
var asdfasdfasdf = 3 + 4;
alert('b finished');
// call the callback
callback();
}
a( function(){ b(function() { alert('both a and b finished'); }); });
This is more formally known as continuation passing style, or CPS.
So, that is an example of the very basics of writing callback functions and basic asynchronous code. Now you can start to use indexedDB. The first thing you will notice is that the function indexedDB.open is documented as asynchronous. So, how can we use it? Like this:
var someGlobalVariable = null;
var openRequest = indexedDB.open(...);
openRequest.onsuccess = function openRequestOnSuccessCallbackFunction(event) {
// Get a reference to the database variable. If this function is ever called at some later
// point in time, the database variable is now defined as the 'result' property of the
// open request. There are multiple ways to access the result property. Any of the following
// lines works 100% the same way. Use whatever you prefer. It does not matter.
var databaseConnection = openRequest.result;
var databaseConnection = this.result;
var databaseConnection = event.target.result;
var databaseConnection = event.currentTarget.result;
// Now that we have a valid and defined databaseConnection variable, do something with it
// e.g.:
var transaction = databaseConnection.transaction(...);
var objectStore = transaction.objectStore(...);
// etc.
// DO NOT DO THE FOLLOWING, it does not work. Why? Review the early descriptions. First off
// this onsuccess callback function does not return anything. Second off this function gets
// called at some *later* point in time, who knows when. It could be a nanosecond later.
someGlobalVariable = databaseConnection;
};
Hopefully that sets you on the path.
Edit: I thought I would add a bit more of an intro. A related concept you need to learn that I did not explain clearly enough regarding control is the difference between imperative and declarative programming.
Imperative programming involves executing a series of statements in the order you wrote. You are the caller and are in control. Imperative code looks like this (fictional code):
var dbConn = dbFactory.newConnection('mydb');
var tx = dbConn.newTransaction();
var resultCode = tx.put(myObject);
if(resultCode == dbResultConstants.ERROR_PUT_KEY_EXISTS) {
alert('uhoh');
}
Declarative programming is subtly different. With a declarative approach, you write functions, and then you register (aka hook or bind) the functions to the JavaScript engine, and then at some point later, when it is appropriate, the engine runs your code. The engine is the caller and is in control, not you. Declarative programming involves callbacks and looks like this (fictional code):
dbFactory.newConnection(function onConnect(dbConn) {
dbConn.newTransaction(function onNewTransaction(tx) {
tx.put(myObject, function onPut(resultCode) {
if(resultCode == dbResultConstants.ERROR_PUT_KEY_EXISTS) {
alert('uhoh');
}
});
});
});
In this example, the only thing you called was the fictional dbFactory.newConnection function. You passed in a callback function. You did not call the callback function yourself. The engine calls the callback function. You cannot call the callback function yourself. This is kind of the whole idea behind why JavaScript engines can allow you to write asynchronous code. Because you don't get to control the order of execution of statements/functions. The engine gets to control it. All you get to do is write your functions, register them, and then start a chain of callbacks (the sole imperative line, the starting statement).
So this is why a function like getObjectStore in your question will not work. You are trying to call the function yourself, but that is backwards. You can only write a function and register it (somehow hook it up as a callback somewhere) and then the engine, not you, calls it at some later point in time.
Hopefully this is not more confusing, but you could actually write your function getObjectStore if you really wanted to by passing in the database variable to the function as its first argument. This leads to the logical next question, how to get a valid database variable to pass into the function. You cannot get one in a global context (reliably). Because the connection variable is only valid within the context of the onOpen callback function. So you would have to make your call to this function from within the onOpen function. Something like:
function getObjectStore(db, name, mode) {
var tx = db.transaction(name, mode);
var store = tx.objectStore(name);
return store;
}
var openRequest = indexedDB.open(...);
openRequest.onsuccess = function onOpen(event) {
// get the connection variable. it is defined within this (onOpen) function and open.
var db = this.result;
// call our simple imperative helper function to get the users store. only call it from
// within this onOpen function because that is the only place we can get the 'db' variable.
var usersStore = getObjectStore(db, 'users', 'readwrite');
// do something here with usersStore, inside this function only.
};

Related

Confusion about callback function in passport serialize/deserialize user

I have a question about asynchronous/synchronous when we write code in JS and I have done google search but I am still a bit confused.
I understand that we use callback functions when we want to make sure that the callback function is executed only after the asynchronous task (such as accessing database) in the outer function is complete. I understand how deserialize and serialize user works.
I am confused why when we do serialize user or deserialize user with passport.js we need a callback function like this ?
passport.serializeUser((user, done) => {
done(null, user.id);
});
If all we want is that the inner arrow function that is passed as an argument to serializeUser() to be executed only after serializeUser() is finished. Or why do we need to pass it as a callback function instead of calling that arrow function below serializeUser() ? I thought JS is synchronous so it will execute the arrow function after serializeUser() is completed anyway ?
I only found serializeUser() documentation in passport documentation on how to use it, but not its implementation so I am also confused whether serializeUser() or deserializeUser()( or any other passport functions) are asynchronous functions ?
Thank you !
It is a fragment of this function on github (https://github.com/jaredhanson/passport/blob/08f57c2e3086955f06f42d9ac7ad466d1f10019c/lib/authenticator.js).
As you can see this function takes a fn parameter. Then it checks if it is a function. If yes it pushes it to this._serializers. Later it does 'some magic' on it, I think it's not important now.
As you can read in jsdoc, serializeUser purpose is to register a function used to serialize user objects. So you pass some function which is invoked later in a code with 2 arguments which labels on that particular function level are user and done.
They left you some space which you can fill with your own code. You can tell how this will behave. They gave you an opportunity to implement your own logic.
Simple example:
function doSomeMagic(fn) {
// I do some my staff
const a = 5;
const b = 10;
// and now I let you decide what to do
// you can implement your own logic in my function
const result = fn(a, b);
console.log(result);
}
doSomeMagic((a, b) => {
return a * b;
});
doSomeMagic((a, b) => {
return a + b;
});
//output:
// 50
// 15
That's exactly what they've done. You can take my function, I give you two arguments do whatever you want. You can multiply, add, substract, whatever you want. I don't have to write separated functions to do math, I can write one, pass values as arguments and let you decided what operation you want to do. And it doesn't mean my code is going to run in an async way.

Uncaught TypeError: Cannot read property 'transaction' of null with an indexeddb

I am getting an error 'Uncaught TypeError: Cannot read property 'transaction' of null '
when in this section of code
remoteDB.indexedDB.addAdmins = function() {
var db = remoteDB.indexedDB.db;
var trans = db.transaction("students", "readwrite");
var request = trans.objectStore("administrators");
/*
this section edited out since the failure is on line 3
*/
request.onsuccess = function(e) {
console.log("success Adding: ", e);
};
request.onerror = function(e) {
console.log(e.value);
};
};
remoteDB.indexedDB.db is null. This appears to be a global variable reference. In order to create a transaction, the variable must be defined, not null, and open.
indexedDB is async. There is no guarantee that when you open a connection to indexedDB and save the handle of the connection in a global variable that the variable is still defined, not null, and open at a later point in time from within the context of another asynchronous function.
It will work sometimes, if you immediately open a transaction. Sometimes the db connection persists. But it is not guaranteed. If there are no open transactions on a database connection then the browser will close the connection at some point there after.
See
Why is db.transaction not working with indexeddb?
Is it bad to open several database connections in indexedDB?
Why is onupgradeneeded never called in this code?
etc.
This error occurs in part usually because of a programmer's lack of familiarity with asynchronous javascript. This is not a criticism, it just seems to be a common pattern. To avoid errors in the future, I suggest spending some time learning about asynchronous javascript.
For example, understand how the following works (or rather why it does not work as expected) before trying to use indexedDB:
var db;
function setDB() {
db = 123;
}
setTimeout(setDB, 10);
console.log('Got a db variable! %s', db);
To really do this justice would be redundant with the thousands of other questions on stackoverflow and insightful articles and guides on the web, but here is an extreme crash course. indexedDB.open is an asynchronous (async) function. The adjective asynchronous means alot. An async function behaves extremely differently than a synchronous function. New javascript programmers generally only learn to program synchronously. So naturally you don't get why calling an async function in your sync code does not work. Sync example:
var a = 1;
var b = 2;
function sum(arg1, arg2) { return arg1 + arg2 }
var abSum = sum(a,b);
console.log('The sum of a + b is %s', abSum);
You know that b=2 is executed after a=1, and that sum=a+b is executed after b. The statements are executed in order, in line, in serial, one after the other, in the order you wrote them. You know that if you tried to put line 4 before line 1 above that it would not work because a and b do not yet have values. In synchronous code, you know that the function sum returns a value. It returns it immediately. So you know that abSum is immediately assigned the return value from calling sum(a,b).
Async code works extremely differently. In general, async functions do not return the value you want. Usually you pass in a function (called a callback function) to the function. An async function only sort of guarantees it will call the callback some time later. It does not return something to use.
var a = 1;
var b = 2;
function asyncSum(arg1,arg2,calledWhenFinished) {
var sum = arg1+arg2;
calledWhenFinished(sum);
return 'asyncSumFinished and called the callback';
}
// The following DOES NOT work as expected
var theResultOfSum = asyncSum(a,b, function(sum) {
console.log('finished. The um is %s', theResultOfSum);
});
// The following DOES work as expected
asyncSum(a,b, function(sum) {
console.log('The sum is %s', sum);
});
Notice that here in the working example, I don't care about what asyncSum returns. After all, it does not return the sum, it just returns a string saying it finished. Now let's do something that is more genuinely async.
function moreGenuineAsyncSum(a,b, callback) {
setTimeout(function() {
var sum = a + b;
console.log('The sum is %s', sum);
callback(sum);
}, 100);
console.log('Requested the sum to be calculated');
return 'Hey, I scheduled the callback function to execute in 100ms';
}
Here I really don't care about the return value of moreGenuineAsyncSum. In fact, it is worthless. It is just a string that says something. Also notice which console.log call gets executed first. The later line gets executed before the earlier line. Out of order. Out of the order in which it was written. Why is that? Because that is what async functions do, they do something at a later point in time.
indexedDB.open is an async function. It returns a IDBOpenRequest object, which is a type of Request object. Most indexedDB functions are async and return Request objects. Request objects do NOT have values. They have callbacks as properties. Therefore:
var dbRequest = indexedDB.open('mydb',1);
dbRequest.onsuccess = function(event) {
// This gets called later. The following are all viable ways to get the IDBDatabase
// object INSIDE THE BLOCK OF THIS FUNCTION ONLY. Any one of the following 3 lines
// works exactly the same way.
var db = this.result;
var db = dbRequest.result;
var db = event.target.result;
console.log('Got a db connection! It is %s', db);
// Now, INSIDE THE BLOCK OF THIS FUNCTION ONLY, do something:
var myTransaction = db.transaction('myObjectStore','readwrite');
var myObjectStore = myTransaction.objectStore('myObjectStore');
// etc.
};
To sum this up before I write an entire book, the emphasis is on the INSIDE THE BLOCK comments above. Inside the block, the 'db' variable is guaranteed to be there, and be open, and be defined, and not null. Outside the block, the db variable does not exist.
So you might be saying, that makes it pretty damn annoying to use indexedDB. You are right, it is annoying. To make it less annoying to you, you can learn about promises. Or you can write callback like functions. Or you can use one of the myriad of design patterns that deal with callback hell. There are many. One is just using a pattern like EventTarget and its relation to the DOM.

Callback function example

I am having a hard time understanding how the callback() function is used in the following code block.
How are we using callback() as a function, in the function body, when function callback() has not been defined?
What are the repercussions of passing true / false as parameters into the callback function below?
I appreciate any clarification, thanks in advance!
socket.on('new user', function(data, callback){
if (nicknames.indexOf(data) != -1){
callback(false);
} else{
callback(true);
socket.nickname = data;
nicknames.push(socket.nickname);
updateUserList();
}
});
When you pass a function as an argument, it is known as a callback function, and when you return a value through this callback function, the value is a parameter of the passed function.
function myFunction(val, callback){
if(val == 1){
callback(true);
}else{
callback(false);
}
}
myFunction(0,
//the true or false are passed from callback()
//is getting here as bool
// the anonymous function below defines the functionality of the callback
function (bool){
if(bool){
alert("do stuff for when value is true");
}else {
//this condition is satisfied as 0 passed
alert("do stuff for when value is false");
}
});
Basically, callbacks() are used for asynchronous concepts. It is invoked on a particular event.
myFunction is also callback function. For example, it occurs on a click event.
document.body.addEventListener('click', myFunction);
It means, first assign the action to other function, and don't think about this. The action will be performed when the condition is met.
I agree with you, the code in the snippet is very unclear.
The answers you got are great, however none refers to the actual use of callback in your code, and I would like to reference that specifically.
First, I will answer your question, and then I will elaborate on the complexity of it.
The answer
turns out socket.io are doing something very cool which is not the standard I know..
socket.io are passing the callback from the front-end to the backend!
So to answer your question what is this callback function - you have to look at your frontend code.
Look for code that looks like this
socket.emit('new user', data, function( booleanParameter ){
// what are you doing with booleanParameter here?
});
I assume that in your case true/false values are meant to pass back to the frontend if new user was added (true) or not (false)..
Or perhaps if the nickname is already in use or not so that the frontend can show an error string if it is..
Basically, #SumanBogati was right in his answer, but I felt it was lacking the step of finding the callback in the front-end due to socket.io's special treatment.
Further Suggestion To make your code clearer
Change name of parameter data to nickname
Add comments - why are you placing nickname on socket?
add documentation
Use jsdocs to explain what the callback is doing
/**
#callback NewUserCallback
#param {boolean} booleanParameter does something..
**/
and then on the function itself
/**
#parameter {string} nickname
#parameter {NewUserCallback} callback
**/
The complexity
Usually, in nodejs, a callback expects the first argument to be an error, so reading your code, it says
socket.on('new user', function(data, callback){
if (nicknames.indexOf(data) != -1){
///// THERE IS NO ERROR
callback(false);
}else{
///// THERE IS AN ERROR
callback(true);
/// do more stuff after the error
socket.nickname = data;
nicknames.push(socket.nickname);
updateUserList();
}
});
Not the pattern you'd expect, is it? I guess this is why you asked the question.
Still the question remains what socket.io's callback means, right? Perhaps their callback does not expect an error as first argument.
I have never used socket.io, and I was unable to find a documentation to clarify this. So I had to download their chat example and debug it ==> and so the answer I gave, they are passing the function from the frontend to the backend.
Socket.io should definitely stress this point in large font in their documentation under a title named "How does socket.io handle callbacks?" or "How does our callbacks work?".
Great question! Learned a lot from it!
I'll try to simplify with a "concrete" example (I hope).
Let's say I have a function that "calculates" the current day and I'll call that function each time I would need the current day ("Don't call us, we'll call you" or whatever).
var getCurrentDay = function (callback) {
var currDate = new Date();
callback(currDate, 'err');
});
};
getCurrentDay(function (returnDay) {
logger.info('Today is: ' + returnDay); });
A callback function, is a function that is passed to another function (let’s call this other function “otherFunction”) as a parameter, and the callback function is called (or executed) inside the otherFunction.
Here is my simple example for callback function
// callback add
function add(a, b){
console.log(a+b);
}
// Main function
function getInput(cb) {
c = 5+5;
d = 6+6;
if (typeof cb === 'function') {
cb(c, d);
}
}
getInput(add)
For detailed explanation refer the this link
Without thinking too much, see the following example.
In the following example, I just call the print function from the add function.
function print( ans ){
console.log(ans) ; // 7
}
function add(a, b){
print(a+b) ;
}
add(2,5);
What if I use the print function as a parameter? Without using print function from global scope I just pass the print function as an argument.
function print( ans ){
console.log(ans) ; // 7
}
function add(a, b, callback){ // here callback = print
callback(a+b) ;
}
add(2,5,print); // print function as a parameter
Generally, JavaScript allows function as a parameter.
So any function that is passed as an argument is called a callback function.
I think now callback is understandable to you.
Callback function mean call after another:)
doHomeWork('math',alertMsg);
Above line said 1. call doHomeWork and then call 2. alertMsg, that's it.:)
function doHomeWork(subject,callback){
console.info("study: "+subject);
callback();
}
alertMsg = function(){
console.info("alert");
}
doHomeWork('math',alertMsg);
Output:
study: math
alert
A callback is any function that is called by another function using parameter
Here a query for you Suppose that Consider how programmers normally write to a file:
- `fileObject = open(file)` //now that we have to wait for the file to open, after that we can write to this file*
- fileObject.write("We are writing to the file.") // but i want to write , not wait
This case - where callbacks are helpful:
//we can pass **writeToFile()** (a callback function) to the open file function
- fileObject = open(file, writeToFile)
//execution continues flowing -- we do not wait for the file to be opened
//once the file is opened we can write to it, but while we wait we can do other things
Function inside a function is called a callback function. Or let me say that the inside function which is present inside the parent function is called the callback function.
If you want to complete a task2 after task1. Then you can make task2 as the callback and this will run asyncronously
Here is one example where the usage of callback function is easy to understand.
A login function which performs user login with the server asynchronously.
Due to asynchronous call , we need to get the result of login once the date receives from the server.
const axios = require('axios');
function login(loginData,callbackSuccess,callBackFailure) {
axios
.post("/api/login",loginData)
.then((response) => {
callbackSuccess(response);
})
.catch((error) => {
callBackFailure(error);
});
}
function callbackSuccess(data) {
console.log("Login response :",data);
}
function callBackFailure(error) {
console.log("Login failed :",error);
}
let userData = {
username : "test",
password : "abcd123"
}
login(userData,callbackSuccess,callBackFailure);

basic node.js callbacks assigned to variable

I've been struggling for awhile with callbacks in general. I'm trying to go back to the basics to try to understand conceptually and this is what I understand so far (yeah, basic).
function RandHash (callback) {
ds_hash = Math.floor((Math.random()*10000)+1);
callback();
}
function CompiledHash (){
console.log(ds_hash);
}
var ds_hash;
RandHash(Compiled Hash);
Will yield the random number.
However, I'm lost as to how to get the "ds_hash" variable returned from the callback.
Seems this would work:
var ds_hash;
ds_hash = RandHash(Compiled Hash);
It doesn't. If I try to return the value something like:
function CompiledHash (){
return ds_hash;
}
It doesn't do anything.
Please help me with this. Seems I spend 90% of my time with node in callback debugging. I've built some decent applications but everything has been handled through the async library because of this mental block I have.
Thanks.
The first error you've made is that RandHash hasn't returned anything. There's no return statement present in the function, so var anything = RandHash(alsoAnything) will always result in anything being undefined.
Functions are first class variables
Functions can be used just as variables can, and passed round as arguments to functions. This is a powerful feature of javascript and something that you'll need to grok to use callbacks. Think of a function as a defined action, and you're just passing that action around.
Also, callbacks should be used to deal with the completion of a process. So the idea is, you know what is meant to happen after process A, and what it is meant to generate, so you can give process A a callback which will be called once A has terminated. Now the scenario here isn't appropriate for a callback situation. It would be much easier for you to do...
function RandHash () {
return Math.floor((Math.random()*10000)+1);
}
And then get your hash by just calling this function, like so...
var hash = RandHash();
You also want to be aware of javascripts variable scoping, as you're missing the var keyword when referencing ds_hash in the RandHash function which means the assignment defaults to global scope. This is probably what is responsible for confusing you, as your assignment of ds_hash in RandHash will be global and therefore available in CompiledHash, meaning some functions will still be able to access the ds_hash value despite this not being the correct, or proper way to do this.
Assuming you will eventually require asynchronous processing
function randHash (callback) {
var hash = /* do hash calculation */
callback(hash);
}
function compileHash (hash) {
/* do something using the hash variable passed to this function */
}
randHash(compileHash); // pass the compileHash function as the callback
You should pass your variables as arguments to the callback. That will deal with your scoping issues hopefully.
Also, small note, functions in javascript should typically be lowercase if they aren't going to be used with the new statement (ie, a javascript class).
To truly understand asynchronous behaviour, you should try it with something that is actually asynchronous, and passing values around, not just a global variable, as that will never work when you move on to asynchronous functions :
function RandHash (callback) {
setTimeout(function() {
var ds_hash = Math.floor((Math.random()*10000)+1);
callback(ds_hash); // execute callback when async operation is done
}, 300);
}
function CompiledHash(hash){ // pass hash
console.log(hash);
}
RandHash(function(returned_hash) { // this is the callback function
CompiledHash(returned_hash); // pass along the returned data
});
The callback function is executed once the asynchronous function, in this case setTimeout, has completed, and passes the data back as an argument.
The callback argument is just the function that is passes as an argument.
// regular function
function doStuff(argument) {
// where argument can be anything, also a function
}
// you can pass a string
doStuff('string');
// or a function
doStuff(function(argument_returned) {
});
// and passing a function you can execute that function with arguments in the original function
function doStuff(argument) {
var argument_returned = 'string';
argument(argument_returned); // executes the passsed function
});
Try this:
function RandHash (callback) {
ds_hash = Math.floor((Math.random()*10000)+1);
callback(ds_hash);
}
function CompiledHash(ds_hash){
console.log(ds_hash);
}
var ds_hash;
RandHash(CompiledHash);
For callbacks and functions, it's a bit difficult to understand variable scopes, and what you are doing is not really recommended. You can pass parameters into callbacks, and should get passed, but I would suggest building it like this:
function RandHash (callback) {
var ds_hash = Math.floor((Math.random()*10000)+1);
callback(ds_hash);
}
function CompiledHash(ds_hash){
console.log(ds_hash);
}
RandHash(CompiledHash);
A couple notes:
If you want to propagate the result of the callback then you need to return its output in RandHash
You don't need ds_hash to be a global variable if you're planning on returning it, anyway
The line RandHash(Compiled Hash); is a syntax error (notice the space in the variable name)
Try this:
function RandHash(callback) {
var ds_hash = Math.floor((Math.random() * 10000) + 1);
return callback(ds_hash);
}
function CompiledHash(ds_hash) {
console.log(ds_hash);
return ds_hash;
}
RandHash(CompiledHash);
Notice that there are no global variables. The callback returns a value and the function that executes the callback passes it along.
On a style note, you should only capitalize the names of functions that you intend to use as a constructor.
This will do
function RandHash (callback) {
var ds_hash = Math.floor((Math.random()*10000)+1);
callback(ds_hash);
}
var CompiledHash = function (ds_hash){
console.log(ds_hash);
}
RandHash(CompiledHash);

Javascript Asynchronous Return Value

Just as I thought I understood JS scope...
Take this code:
function init() {
var page;
var pageName = $('body').data('page');
switch(pageName) {
// (there are more switch cases here normally...)
case 'pSearchLatest':
require(['searchresults'], function (SearchResults) {
page = new SearchResults().init();
console.log(page); // <- shows the object!
});
default:
break;
}
console.log(page); // <- undefined
return page;
}
See the console log comments. Why is the second returning undefined when the var page is declared outside of the scope of the switch statement?
EDIT
So I mistakenly thought this was a scope issue but it's down to the asynchronous nature of AMD.
How can I return the value of page in the require scope in the same method without a while loop checking for undefined?
EDIT 2
I did it like this:
function init(callback) {
case 'pSearchLatest':
require(['searchresults'], function (SearchResults) {
page = new SearchResults().init();
callback(page)
});
}
and in my page that calls the wrapping init() method:
new PageController().init(function(asyncViewController) {
App.view = asyncViewController;
});
You did understand scope correctly. The code in that inner function does indeed assign to the variable in the outer scope.
What you did not understand is asynchronous behaviour. The require function takes a callback function which will be invoked somewhen in the future - when the requested module is available. Yet, it does immediately return and control flow will lead to the console.log statement, which does print undefined - which is the value the page variable has at that time.
You should be able to recognise that since the undefined is logged first, and the log statement in the callback (with the object) executes later, even though it comes above in the code.
require is asynchronous, therefore your function did exit first and then processed callback function.
Two possible reasons... you didnt match the case. or the callback where the assignment of the value to page happens hasnt been executed yet. Im guessing its the latter since it would be readily apparent if you case was failing. Look into the documentation for whatever AMD library youre using and see how the execution of the function(s) passed to require are executed.
Also as a generaly rule try to avoid returning values determined by asynchronous calls like this. Instead use some kind of Observer pattern to subscribe to a particular event that can be triggered from different places.

Categories

Resources