Failed to execute 'put' on 'IDBObjectStore': The transaction has finished - javascript

I am trying to update an entry in my simple to do app with indexedDB, however I am getting Failed to execute 'put' on 'IDBObjectStore': The transaction has finished.
I can't seem to figure out why it won't finish the transaction, I tried the debugger and it stops at this line: var updateNameRequest = tasksStore.put( requestForItem.result.name, Number(requestForItem.result.id)) Please see the snippet I included below. For additional context creating, reading, and deleting work just fine it's just updating data that I'm having trouble with
I also tried to implement the openCursor technique which I got from Mozilla which I commented out since it also doesn't work (I get the same behavior) Check out my repo I know it's still very messy :(
const request = window.indexedDB.open("toDoList", 2);
var db;
request.onsuccess = function (event) {
console.log("check out some data about our opened db: ", request.result);
db = event.target.result; // result of opening the indexedDB instance "toDoList"
getTasks(); //just a function to retrieve data
};
$(document).on("click", ".editBtn", function () {
var transaction = db.transaction("tasks", "readwrite");
var tasksStore = transaction.objectStore("tasks");
console.log(tasksStore);
let taskId = $(this).attr("idNo");
var requestForItem = tasksStore.get(Number(taskId));
requestForItem.onsuccess = function () {
// console.log(requestForItem.result)
var oldData = requestForItem.result;
// prepopulate the input
$(".editInput").val(requestForItem.result.name);
$(".saveBtn").click(function () {
requestForItem.result.name = $(".editInput").val().trim()
console.log( requestForItem.result)
var updateNameRequest = tasksStore.put( requestForItem.result.name, Number(requestForItem.result.id))
console.log("-------------", updateNameRequest.transaction) // doesn't get to this line
updateNameRequest.onerror = function() {
console.log("something went wrong")
console.log(updateNameRequest.error)
};
updateNameRequest.onsuccess = function() {
console.log("here")
$(".editInput").val("")
getTasks();
};
});
};

Indexed DB transactions auto-commit when all requests have been completed and no further requests were made before control returns to the event loop. Put another way - you can make new requests in the success or error callback from a previous request, but not in other asynchronous callbacks such as event handlers.
You need to start a new transaction within the click handler, because any previous transaction will have autocommitted.

Related

IndexedDB's callbacks not being executed inside the 'fetch' event of a Service Worker

I'm trying to do a couple of things in the IndexedDB database inside the 'fetch' event of a service worker, when the aplication asks the server for a new page. Here's what I'm going for:
Create a new object store (they need to be created dynamically, according to the data that 'fetch' picks up);
Store an element on the store.
Or, if the store already exists:
Get an element from the store;
Update the element and store it back on the store.
The problem is that the callbacks (onupgradeneeded, onsuccess, etc) never get executed.
I've been trying with the callbacks inside of each other, though I know that may not be the best approach. I've also tried placing an event.waitUntil() on 'fetch' but it didn't help.
The 'fetch' event, where the function registerPageAccess is called:
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
event.waitUntil(function () {
const nextPageURL = new URL(event.request.url);
if (event.request.destination == 'document') {
if (currentURL) {
registerPageAccess(currentURL, nextPageURL);
}
currentURL = nextPageURL;
}
}());
/*
* some other operations
*/
return response || fetch(event.request);
})
);
});
registerPageAccess, the function with the callbacks.
I know it's plenty of code, but just look at secondRequest.onupgradeneeded in the 5th line. It is never executed, let alone the following ones.
function registerPageAccess(currentPageURL, nextPageURL) {
var newVersion = parseInt(db.version) + 1;
var secondRequest = indexedDB.open(DB_NAME, newVersion);
secondRequest.onupgradeneeded = function (e) {
db = e.target.result;
db.createObjectStore(currentPageURL, { keyPath: "pageURL" });
var transaction = request.result.transaction([currentPageURL], 'readwrite');
var store = transaction.objectStore(currentPageURL);
var getRequest = store.get(nextPageURL);
getRequest.onsuccess = function (event) {
var obj = getRequest.result;
if (!obj) {
// Insert element into the database
console.debug('ServiceWorker: No matching object in the database');
const addRes = putInObjectStore(nextPageURL, 1, store);
addRes.onsuccess = function (event) {
console.debug('ServiceWorker: Element was successfully added in the Object Store');
}
addRes.onerror = function (event) {
console.error('ServiceWorker error adding element to the Object Store: ' + addRes.error);
}
}
else {
// Updating database element
const updRes = putInObjectStore(obj.pageURL, obj.nVisits + 1, store);
updRes.onsuccess = function (event) {
console.debug('ServiceWorker: Element was successfully updated in the Object Store');
}
updRes.onerror = function (event) {
console.error('ServiceWorker error updating element of the Object Store: ' + putRes.error);
}
}
};
};
secondRequest.onsuccess = function (e) {
console.log('ServiceWorker: secondRequest onsuccess');
};
secondRequest.onerror = function (e) {
console.error('ServiceWorker: error on the secondRequest.open: ' + secondRequest.error);
};
}
I need a way to perform the operations in registerPageAccess, which involve executing a couple of callbacks, but the browser seems to kill the Service Worker before they get to occur.
All asynchronous logic inside of a service worker needs to be promise-based. Because IndexedDB is callback-based, you're going to find yourself needing to wrap the relevant callbacks in a promise.
I'd strongly recommend not attempting to do this on your own, and instead using one of the following libraries, which are well-tested, efficient, and lightweight:
idb-keyval, if you're okay with a simple key-value store.
idb if you're need the full IndexedDB API.
I'd also recommend that you consider using the async/await syntax inside of your service worker's fetch handler, as it tends to make promise-based code more readable.
Put together, this would look roughly like:
self.addEventListener('fetch', (event) => {
event.waitUntil((async () => {
// Your IDB cleanup logic here.
// Basically, anything that can execute separately
// from response generation.
})());
event.respondWith((async () => {
// Your response generation logic here.
// Return a Response object at the end of the function.
})());
});

How to dynamically create and populate multiple objectStores on HTML5 IndexedDB in the same transaction?

Working on the IndexedDB API, I'm creating many objectStores that belong to the same database, in one transaction, when the user loads a webpage.
I order to do so, I created an object which contains many objectStores to be created, each one has it's name, data and index.
Then a function runs the object and effectively creates Database, objectStores and indexes for each one.
However of all OS's created, just the last member of the object gets populated. Say of 5 objects to be created and populated, 5 are created but only the last one is populated.
Clearly is a problem of overwriting or some issue related to the JS stack or asynchronicity.
I appreciate any help to make the code populate all OS not the last one.
My browser is Chrome 56, I fetch data from an API whose response is OK, and I'm coding on vanillajs. I appreciate your help in vanillajs, there is no way to use any library or framework different from what the modern Web Platform offers.
Here is the code:
On the HTML side, this is an example of the object:
var datastores = [{osName:'items', osEndpoint: '/api/data/os/1/1', osIndex:'value'}, {osName:'categories', osEndpoint: '/api/data/os/2/1', osIndex: 'idc'}];
On javascript:
var request = indexedDB.open(DB_NAME, DB_VERSION); // open database.
request.onerror = function (e) { // error callback
console.error("error: " + e.target.errorCode);
};
request.onupgradeneeded = function (e) { // the onupgradeneeded event which creates all schema, dataabase, objectstores and populates OS.
var db = this.result;
for (var i in datastores) { // loop the objectStore object.
var objectStore = db.createObjectStore(datastores[i].osName, {keyPath: "id"});
TB_NAME = datastores[i].osName; // instantiate each objectStore name.
objectStore.createIndex(datastores[i].osIndex, datastores[i].osIndex, { unique: false }); // create each index.
objectStore.transaction.oncomplete = function(e) { // oncomplete event, after creating OS...
fetchGet(datastores[i].osEndpoint, popTable); // runs a function to fetch from a designated endpoint and calls a function.
};
}
}
Now the functions: to fetch data and to populate data:
function fetchGet(url, function) { // fetch from API.
fetch(url, {
method: 'GET'
}).then(function(response) {
return response.json();
}).then(function(json) {
popTable (json);
}).catch(function(err) {
console.log('error!', err);
});
}
function popTable(json) {
var m = 0;
var tx = db.transaction(TB_NAME, "readwrite");
tx.oncomplete = function(e) {
console.log("Completed Transaction " + TB_NAME);
};
tx.onerror = function(e) {
console.error("error: " + e.target.errorCode);
};
var txObjectStore = tx.objectStore(TB_NAME);
for (m in json) {
var request = txObjectStore.add(json[m]);
request.onsuccess = function (e) {
console.log('adding... ' );
};
}
}
The for (var i in datastores) loop runs synchronously, updating the global TB_NAME variable every time. When the loop finishes, TB_NAME will be holding the name of the last object store.
By the time the asynchronous popTable calls run, TB_NAME will forever be holding the name of the last store, so that's the only one that will update. Try adding logging to popTable to see this.
You'll need to pass the current value of the store name along somehow (e.g. as an argument to fetchGet). Also note that although you pass popTable as a parameter when calling fetchGet you're not actually accepting it as an argument.
...
Specific changes:
Change how you call fetchGet to include the store name:
fetchGet(datastores[i].osEndpoint, popTable, datastores[i].osName);
Change the fetchGet function to accept the args:
function fetchGet(url, func, name) {
And then instead of calling popTable directly, do:
func(json, name);
And then change the definition of popTable to be:
function popTable(json, name) {
... and use name in the transaction.

How to read Object from Firebase using AngularJS

I am developing my app, and one of the features will be messaging within the application. What I did, is I've developed 'send message' window, where user can send message to other user. The logic behind it is as following:
1. User A sends message to User B.
2. Firebase creates following nodes in 'Messaging':
"Messaging"->"User A"->"User B"->"Date & Time"->"UserA: Message"
"Messaging"->"User B"->"User A"->"Date & Time"->"UserA: Message"
Here is the code that I am using for sending messages:
sendMsg: function(receiver, content) {
var user = Auth.getUser();
var sender = user.facebook.id;
var receiverId = receiver;
var receiverRef = $firebase(XXX.firebase.child("Messaging").child(receiverId).child(sender).child(Date()));
var senderRef = $firebase(XXX.firebase.child("Messaging").child(sender).child(receiverId).child(Date()));
receiverRef.$set(sender,content);
senderRef.$set(sender,content);
},
(picture 1 in imgur album)
At the moment, I am trying to read the messages from the database, and sort them in according to date. What I've accomplished so far, is that I have stored the content of "Messaging/UserA/" in form of an Object. The object could be seen in the picture I've attached (picture 2).
http://imgur.com/a/3zQ0o
Code for data receiving:
getMsgs: function () {
var user = Auth.getUser();
var userId = user.facebook.id;
var messagesPath = new Firebase("https://xxx.firebaseio.com/Messaging/");
var Messages = messagesPath.child(userId);
Messages.on("value", function (snapshot) {
var messagesObj = snapshot.val();
return messagesObj;
}, function (errorObject) {
console.log("Error code: " + errorObject.code);
});
}
My question is: how can I read the object's messages? I would like to sort the according to the date, get the message and get the Id of user who has sent the message.
Thank you so much!
You seem to be falling for the asynchronous loading trap when you're reading the messages:
getMsgs: function () {
var user = Auth.getUser();
var userId = user.facebook.id;
var messagesPath = new Firebase("https://xxx.firebaseio.com/Messaging/");
var Messages = messagesPath.child(userId);
Messages.on("value", function (snapshot) {
var messagesObj = snapshot.val();
return messagesObj;
}, function (errorObject) {
console.log("Error code: " + errorObject.code);
});
}
That return statement that you have in the Messages.on("value" callback doesn't return that value to anyone.
It's often a bit easier to see what is going on, if we split the callback off into a separate function:
onMessagesChanged(snapshot) {
// when we get here, either the messages have initially loaded
// OR there has been a change in the messages
console.log('Inside on-value listener');
var messagesObj = snapshot.val();
return messagesObj;
},
getMsgs: function () {
var user = Auth.getUser();
var userId = user.facebook.id;
var messagesPath = new Firebase("https://xxx.firebaseio.com/Messaging/");
var Messages = messagesPath.child(userId);
console.log('Before adding on-value listener');
Messages.on("value", onMessagesChanged);
console.log('After adding on-value listener');
}
If you run the snippet like this, you will see that the console logs:
Before adding on-value listener
After adding on-value listener
Inside on-value listener
This is probably not what you expected and is caused by the fact that Firebase has to retrieve the messages from its servers, which could potentially take a long time. Instead of making the user wait, the browser continues executing the code and calls your so-called callback function whenever the data is available.
In the case of Firebase your function may actually be called many times, whenever a users changes or adds a message. So the output more likely will be:
Before adding on-value listener
After adding on-value listener
Inside on-value listener
Inside on-value listener
Inside on-value listener
...
Because the callback function is triggered asynchronously, you cannot return a value to the original function from it. The simplest way to work around this problem is to perform the update of your screens inside the callback. So say you want to log the messages, you'd do:
onMessagesChanged(snapshot) {
// when we get here, either the messages have initially loaded
// OR there has been a change in the messages
console.log('Inside on-value listener');
var i = 0;
snapshot.forEach(function(messageSnapshot) {
console.log((i++)+': '+messageSnapshot.val());
});
},
Note that this problem is the same no matter what API you use to access Firebase. But the different libraries handle it in different ways. For example: AngularFire shields you from a lot of these complexities, by notifying AngularJS of the data changes for you when it gets back.
Also see: Asynchronous access to an array in Firebase

Properties/methods cant be initialised indexedDB

Couldn't understand why this is happening:
var request=window.indexedDB.open("known"); //async IDB request
request.onsuccess=function(){db=event.target.result;
alert("database created"+db); //it works fine database created
var store=db.createObjectStore("friends",{pathKey:"name"})
//error **"Uncaught InvalidStateError: An operation was called on an object on which it is not allowed or at a time when it is not allowed."** as on console box
}
When db has been assigned with reference to Database "known" then why error pops up?
You can only call createObjectStore when you are in a versionchange transaction, which rougly corresponds to the upgradeneeded event handler. Also, it's "keyPath", not "pathKey". Try
var request=window.indexedDB.open("known", 2); //async IDB request
request.onupgradeneeded = function() {
console.log("got upgradeneeded event");
db = event.target.result;
var store = db.createObjectStore("friends", {keyPath: "name"});
}
request.onsuccess=function(){
console.log("got success event");
db=event.target.result;
}
There are some good examples in the spec.
Looks like you forgot to name the argument to the callback? Try:
request.onsuccess = function(event) ...
This way, "event" is defined.

onsuccess and oncomplete call back is not working for indexedDb add transaction

I am trying to have a function in the onsuccess callback for IndexedDb add transaction, but for some reason the onsuccess callback is never called. I basically tried to add a movie object to the IndexedDb and in the callback I try to display all the movies in the indexedDb by iterating cursor. I hope that the newly added movie will also be displayed. But the callback is failing. Below is my code. Could someone please let me know what is the problem?
var movieName=document.getElementById('movieInput').value;
var movieDataToStore = [{ movieid: "5", name: movieName, runtime:"60"}];
var request = indexedDB.open("movies", 1);
request.onsuccess = function(event) {
db = event.target.result;
//var transaction = window.db.transaction(["movies"], "readwrite");
//alert(db.transaction("movies").objectStore("movies").add(null));
var requestDataadd=window.db.transaction(["movies"],"readwrite").objectStore("movies").add(movieDataToStore[0]);
requestDataadd.onsuccess = function(event) {
window.db.transaction("movies").objectStore("movies").openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
alert("CURSOR: movie: " + cursor.key + " has name " + cursor.value.name);
cursor.continue();
} else {//writeLog("CURSOR: No more entries!");
alert("Cursor at the Load Button unabe to open");
}
};
};
};
You are using two transactions. Since the second transaction is created before the first is completed, both of them are getting snapshot of initial state.
You have to reuse the first transaction or wait until it is completed to start second transaction. Here is reusing the transaction:
var tx = window.db.transaction(["movies"],"readwrite");
var requestDataadd = tx.objectStore("movies").add(movieDataToStore[0]);
requestDataadd.onsuccess = function(event) {
tx.objectStore("movies").openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
alert("CURSOR: movie: " + cursor.key + " has name " + cursor.value.name);
cursor.continue();
} else {//writeLog("CURSOR: No more entries!");
alert("Cursor at the Load Button unabe to open");
}
};
};
Are you getting the alert "Cursor at the Load Button unabe to open" ?
On first glance, I think the problem would be that the requestDataadd request fails because you already have inserted this object once before (successfully), and so you get a duplicate key error. But you don't have an onerror listener defined for the requestDataadd request.
However, that can't be the case if the onsuccess listener of requestDataadd actually is called (and you get an alert).
I also see you don't have an onerror listener defined for the openCursor request. You might want to change that to get more insight. Generally, you should always define both onerror and onsuccess handlers.

Categories