Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
           }; 
         };
like image 310
Jerry M Avatar asked Jun 17 '14 05:06

Jerry M


1 Answers

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.

like image 118
Josh Avatar answered Sep 29 '22 03:09

Josh