I have an OK understanding of JS but am ultimately still learning. I'm trying to recreate a PHP/mySQL project over to IndexedDB and can't work out why I'm seeing an error here.
The IndexedDB connection works as expected. The first function (createItem) functions fine, however the second function (getItems) is returning an error claiming that the "db" variable is undefined. It's a global variable so should be accessible by the function's scope, and the createItem function has no problem using it. Can anyone help me see what I've missed here.
// GLOBAL DB VAR
var db;
// WAIT FOR DOM
document.addEventListener("DOMContentLoaded", function(){
// IF INDEXED DB CAPABLE
if("indexedDB" in window) {
// OPEN DB
var openRequest = indexedDB.open("newTabTest",1);
// CREATE NEW / UPDATE
openRequest.onupgradeneeded = function(e) {
// Notify user here: creating database (first time use)
var thisDB = e.target.result;
// Create "items" table if it doesn't already exist
if(!thisDB.objectStoreNames.contains("items")) {
var store = thisDB.createObjectStore("items", {keyPath: "id", autoIncrement: true});
store.createIndex("name","name", {unique:true});
store.createIndex("folder","folder", {unique:false});
store.createIndex("dial","dial", {unique:false});
}
}
openRequest.onsuccess = function(e) {
// Success- set db to target result.
db = e.target.result;
}
openRequest.onerror = function(e) {
// DB ERROR :-(
}
}
},false);
// CREATE ITEM FUNCTION
function createItem(n,u,f,c) {
var transaction = db.transaction(["items"],"readwrite");
var store = transaction.objectStore("items");
var item = {
name: n,
url: u,
folder: f,
colour: c,
dial: 0,
order: 100
}
var request = store.add(item);
request.onerror = function(e) {
console.log("An error occured.");
}
request.onsuccess = function(e) {
console.log("Successfully added.")
}
};
// GET ITEM(S) FUNCTION
// Specify index and key value OR omit for all
function getItems(callback, ind, key) {
var transaction = db.transaction(["items"],"readonly");
var store = transaction.objectStore("items");
var response = [];
// If args are omitted - grab all items
if(!ind | !key) {
var cursor = store.openCursor();
cursor.onsuccess = function(e) {
var res = e.target.result;
if(res) {
var r = {
"name": res.value['name'],
"url": res.value['url'],
"folder": res.value['folder'],
"colour": res.value['colour'],
"dial": res.value['dial'],
"order": res.value['order']
};
response.push(r);
res.continue();
}
}
cursor.oncomplete = function() {
callback(response);
}
} else {
// If both args are specified query specified index
store = store.index(ind);
var range = IDBKeyRange.bound(key, key);
store.openCursor(range).onsuccess = function(e) {
var res = e.target.result;
if(res) {
var r = {
"name": res.value['name'],
"url": res.value['url'],
"folder": res.value['folder'],
"colour": res.value['colour'],
"dial": res.value['dial'],
"order": res.value['order']
};
response.push(r);
res.continue();
}
}
cursor.oncomplete = function() {
callback(response);
}
}
};
As you've figured out in comments, you do have to stick the db-dependent actions inside a function called from a success handler.
This callback-based programming quickly becomes a pain, and a common solution for that is to use promises.
However, making IndexedDB work with promises is still work-in-progress (see https://github.com/inexorabletash/indexeddb-promises if you're interested).
If your goal is to get something done (and not to learn the bare IndexedDB APIs), perhaps you'd be better off finding a wrapper library for IndexedDB (can't recommend one though, since I've not tried working seriously with IDB yet).
I would not recommend using a variable like 'db' as you have in your example. If you are new to reading and writing asynchronous Javascript, you are just going to cause yourself a lot of pain. There are better ways to do it. It takes several pages to explain and is explained in many other questions on StackOverflow, so instead, very briefly, consider rewriting your code to do something like the following:
function createItem(db, ...) {
var tx = db.transaction(...);
// ...
}
function openIndexedDBThenCreateItem(...) {
var openRequest = indexedDB.open(...);
openRequest.onsuccess = function(event) {
var db = event.target.result;
createItem(db, ...);
};
}
function getItems(db, callback, ...) {
var tx = db.transaction(...);
var items = [];
tx.oncomplete = function(event) {
callback(items);
};
// ...
var request = store.openCursor(...);
request.onsuccess = function(event) {
var request = event.target;
var cursor = event.target.result;
if(cursor) {
var item = cursor.value;
items.push(item);
cursor.continue();
}
};
}
function openIndexedDBThenGetItems(db, callback, ...) {
var openRequest = indexedDB.open(...);
openRequest.onsuccess = function(event) {
var db = event.target.result;
getItems(db, callback, ...);
};
}
Also, you don't need to wait for DOMContentLoaded to start using indexedDB. It is immediately available.
If you get the above code, then you can consider a further improvement of adding a simple helper function:
function openIndexedDB(callback) {
var openRequest = indexedDB.open(...);
openRequest.onerror = callback;
openRequest.onsuccess = callback;
}
And then rewrite the examples like this:
function openIndexedDBThenCreateItem(...) {
openIndexedDB(function onOpen(event) {
if(event.type !== 'success') {
console.error(event);
} else {
var db = event.target.result;
createItem(db, ...);
}
});
}
function openIndexedDBThenGetItems(...) {
openIndexedDB(function onOpen(event) {
if(event.type !== 'success') {
console.error(event);
} else {
var db = event.target.result;
getItems(db, ...);
}
});
}
Related
I want to execute this query
select * from properties where propertyCode IN ("field1", "field2", "field3")
How can I achieve this in IndexedDB
I tried this thing
getData : function (indexName, params, objectStoreName) {
var defer = $q.defer(),
db, transaction, index, cursorRequest, request, objectStore, resultSet, dataList = [];
request = indexedDB.open('test');
request.onsuccess = function (event) {
db = request.result;
transaction = db.transaction(objectStoreName);
objectStore = transaction.objectStore(objectStoreName);
index = objectStore.index(indexName);
cursorRequest = index.openCursor(IDBKeyRange.only(params));
cursorRequest.onsuccess = function () {
resultSet = cursorRequest.result;
if(resultSet){
dataList.push(resultSet.value);
resultSet.continue();
}
else{
console.log(dataList);
defer.resolve(dataList);
}
};
cursorRequest.onerror = function (event) {
console.log('Error while opening cursor');
}
}
request.onerror = function (event) {
console.log('Not able to get access to DB in executeQuery');
}
return defer.promise;
But didn't worked. I tried google but couldn't find exact answer.
If you consider that IN is essentially equivalent to field1 == propertyCode OR field2 == propertyCode, then you could say that IN is just another way of using OR.
IndexedDB cannot do OR (unions) from a single request.
Generally, your only recourse is to do separate requests, then merge them in memory. Generally, this will not have great performance. If you are dealing with a lot of objects, you might want to consider giving up altogether on this approach and thinking of how to avoid such an approach.
Another approach is to iterate over all objects in memory, and then filter those that don't meet your conditions. Again, terrible performance.
Here is a gimmicky hack that might give you decent performance, but it requires some extra work and a tiny bit of storage overhead:
Store an extra field in your objects. For example, plan to use a property named hasPropertyCodeX.
Whenever any of the 3 properties are true (has the right code), set the field (as in, just make it a property of the object, its value is irrelevant).
When none of the 3 properties are true, delete the property from the object.
Whenever the object is modified, update the derived property (set or unset it as appropriate).
Create an index on this derived property in indexedDB.
Open a cursor over the index. Only objects with a property present will appear in the cursor results.
Example for 3rd approach
var request = indexedDB.open(...);
request.onupgradeneeded = upgrade;
function upgrade(event) {
var db = event.target.result;
var store = db.createObjectStore('store', ...);
// Create another index for the special property
var index = store.createIndex('hasPropCodeX', 'hasPropCodeX');
}
function putThing(db, thing) {
// Before storing the thing, secretly update the hasPropCodeX value
// which is derived from the thing's other properties
if(thing.field1 === 'propCode' || thing.field2 === 'propCode' ||
thing.field3 === 'propCode') {
thing.hasPropCodeX = 1;
} else {
delete thing.hasPropCodeX;
}
var tx = db.transaction('store', 'readwrite');
var store = tx.objectStore('store');
store.put(thing);
}
function getThingsWherePropCodeXInAnyof3Fields(db, callback) {
var things = [];
var tx = db.transaction('store');
var store = tx.objectStore('store');
var index = store.index('hasPropCodeX');
var request = index.openCursor();
request.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
var thing = cursor.value;
things.push(thing);
cursor.continue();
} else {
callback(things);
}
};
request.onerror = function(event) {
console.error(event.target.error);
callback(things);
};
}
// Now that you have an api, here is some example calling code
// Not bothering to promisify it
function getData() {
var request = indexedDB.open(...);
request.onsuccess = function(event) {
var db = event.target.result;
getThingsWherePropCodeXInAnyof3Fields(db, function(things) {
console.log('Got %s things', things.length);
for(let thing of things) {
console.log('Thing', thing);
}
});
};
}
Lately I've been trying to implement WebRTC datachannels in Haxe, but come across a great deal of difficulty. When I use
dataChannel.send();
there appears to be no effect, despite the data channel supposedly being successfully opened.
The (extremely inefficient and messy) code I'm using looks like this:
package arm;
import haxe.Json;
import js.html.rtc.*;
import js.html.Document;
import js.html.WebSocket;
import js.html.DataElement;
#:expose
class DataChannelManager extends iron.Trait {
var user = "gobbledygook";
var first = false;
var initiator = true;
public function new() {
super();
var document = new Document();
var ws:js.html.WebSocket;
var config = {"iceServers":[{"url":"stun:stun.l.google.com:19302"}]};//temporary arrangement
//var optional:Array<Dynamic> = [{'DtlsSrtpKeyAgreement': true}, {'RtcDataChannels': true }];
// var connection:Dynamic = {
// 'optional'://try changing this to mandatory some time
// optional
// };
var peerConnection = new PeerConnection(config);
var dataChannel:js.html.rtc.DataChannel;
var ready = false;
function sendNegotiation(type, sdp) {
var json = {user:user/*, theloc:myloc*/, action: type, data: sdp};
ws.send(Json.stringify(json));
trace("Negotiation of type "+json.action);
}
var sdpConstraints = {
offerToReceiveAudio: false,
offerToReceiveVideo: false
};
var dcOpen=false;
notifyOnInit(function() {
var optionalStruct:Dynamic = {reliable: true}
dataChannel = peerConnection.createDataChannel("datachannel", optionalStruct);
dataChannel.onmessage = function(e){trace("DC message:" +e.data);};
dataChannel.onopen = function(){trace("-DC OPENED");dcOpen=true;};
dataChannel.onclose = function(){trace("-DC closed!");};
dataChannel.onerror = function(){trace("DC ERROR");};
trace("intialization!");
});
var firstfirst=true;
notifyOnUpdate(function() {
if (dcOpen) {
dcOpen=false;
trace("sending...");
dataChannel.send("stuff!");
}
if (firstfirst&&object.properties['go']) {
user=object.properties['string'];
first=true;
firstfirst=false;
// if (initiator) {
// peerConnection.createOffer(sdpConstraints).then(function (sdp) {
// peerConnection.setLocalDescription(sdp);
// sendNegotiation("offer", sdp);
// trace("SEND OFFER");
// }, function (data) {
// trace("Offer creation failure,", data);
// });
// } else {
// peerConnection.createAnswer(sdpConstraints).then(function (sdp) {
// trace("Answer made.");
// peerConnection.setLocalDescription(sdp);
// sendNegotiation("answer", sdp);
// });
// }
}
if (first) {
first=false;
ws = new WebSocket("ws://----------/*yes, there's an ip here*/:8080");
ws.onopen = function() {
trace("ws opened!");
peerConnection.onicecandidate = function(event) {
trace("ICE offer ready");
if (peerConnection==null || event ==null || event.candidate == null) return;
sendNegotiation("candidate", event.candidate);
}
if (initiator) {
trace("initiating");
// var optionalStruct:Dynamic = {reliable: true}
// dataChannel = peerConnection.createDataChannel("datachannel", optionalStruct);
// dataChannel.onmessage = function(e){trace("DC message:" +e.data);};
// dataChannel.onopen = function(){trace("-DC OPENED");dcOpen=true;};
// dataChannel.onclose = function(){trace("-DC closed!");};
// dataChannel.onerror = function(){trace("DC ERROR");};
peerConnection.createOffer(/*sdpConstraints*/).then(function (sdp) {
peerConnection.setLocalDescription(sdp);
sendNegotiation("offer", sdp);
trace("SEND OFFER");
}, function (data) {
trace("Offer creation failure,", data);
});
}
ws.onmessage = function (data) {
//var info=data.data.split()
if (data.data=="connected!") {return;}
var adata = Json.parse(data.data.substring(5));
if (adata.action=="offer") {
trace("Offer recieved.");
// var optionalStruct:Dynamic = {reliable: true}
// dataChannel = peerConnection.createDataChannel("datachannel", optionalStruct);
// dataChannel.onmessage = function(e){trace("DC message:" +e.data);};
// dataChannel.onopen = function(){trace("DC OPENED");dcOpen=true;};
// dataChannel.onclose = function(){trace("DC CLOSED");};
// dataChannel.onerror = function(){trace("DC ERROR");};
peerConnection.setRemoteDescription(/*try variations here*/ adata.data);
peerConnection.createAnswer(sdpConstraints).then(function (sdp) {
trace("Answer made.");
peerConnection.setLocalDescription(sdp);
sendNegotiation("answer", sdp);
});
}
if (adata.action=="answer") {
trace("Answer recieved.");
peerConnection.setRemoteDescription(/*try variations here*/ adata.data);
}
if (adata.action=="candidate") {
trace("ICE candidate recieved, looks like:",adata);
var soItDoesntComplain:Dynamic = adata.data;
peerConnection.addIceCandidate(soItDoesntComplain);
}
}
}
}
if (ready) {
trace("connected to net");
}
});
// notifyOnRemove(function() {
// });
}
}
You will notice a great deal of code is commented out -- I was expirementing with moving the dataChannel creation around.
For a better idea of what the issue is, here is the console output for the recieving and initiating clients, respectively:
In case you are wondering, notifyOnInit gets a function that is executed once at the beginning, and notifyOnUpdate gets a function called at a regular interval. object.properties['go'] is set by a different class when the username is given.
The JS api is basically the same (as far as I can tell, I haven't used WebRTC at all in the past), I haven't noticed any differences yet and I'm very sure that my issue is my fault and not Haxe's.
Thank you to those who answer.
this is not answer.
Anxious point.
peerCoonection.createOffer()
peerCoonection.createAnswer()
peerCoonection.setLocalDescription()
peerCoonection.setRemoteDescription()
peerCoonection.addIceCandidate()
are await is required.
In my node.js app, reading data from MSSQL using tedious, I'm calling the below every 1 second:
Fetch the data from the server (fetchStock function) and save it in temporary array
Send the data saved in the temporary array to the client using the Server-Sent Events (SSE) API.
It looks the 1 second is not enough to recall the fetchStock function before the previous call is completely executed, so I get execution errors from time to time.
I increased it to 5 seconds, but still get the same issue every once in a while.
How can I use Promise().then to be sure the fetchStock function is not re-called before the previouse call be completely executed?
var Request = require('tedious').Request;
var Connection = require('tedious').Connection;
var config = {
userName: 'sa',
password: 'pswd',
server: 'xx.xxx.xx.xxx',
options: {
database: 'DB',
rowCollectionOnRequestCompletion: 'true',
rowCollectionOnDone: 'true'
},
};
var sql = new Connection(config);
var addElem = (obj, elem)=> [].push.call(obj, elem);
var result = {}, tmpCol = {}, tmpRow = {};
module.exports = {
displayStock: function (es) {
var dloop = setInterval(function() {
if(result.error !== null)
if (es) es.send(JSON.stringify(result), {event: 'rmSoH', id: (new Date()).toLocaleTimeString()});
if(result.error === null)
if (es) es.send('connection is closed');
}, 1000);
},
fetchStock: function () {
request = new Request("SELECT ItemCode, WhsCode, OnHand FROM OITW where OnHand > 0 and (WhsCode ='RM' or WhsCode ='FG');", function(err, rowCount, rows) {
if (err) {
result = {'error': err};
console.log((new Date()).toLocaleTimeString()+' err : '+err);
}
if(rows)
rows.forEach(function(row){
row.forEach(function(column){
var colName = column.metadata.colName;
var value = column.value;
addElem(tmpCol, {colName: value})
});
addElem(tmpRow,{'item': tmpCol[0].colName, 'Whs': tmpCol[1].colName, 'Qty': tmpCol[2].colName});
tmpCol = {};
});
result = tmpRow;
tmpRow={}
});
sql.execSql(request);
}
}
I think what you need is a simple variable to check if there's already running request not Promise.
var latch = false;
// It will be called only if the previous call is completed
var doFetchStock = () => sql.execSql(new Request("SQL", (err, rowCount, rows) => {
// Your logic dealing with result
// Initializes the latch
latch = false;
});
module.exports = {
fetchStock: function () {
// Check if the previous request is completed or not
if (!latch) {
// Sets the latch
latch = true;
// Fetches stock
doFetchStock();
}
}
};
Actually I've used this kind of pattern a lot to allow some behavior only once.
https://github.com/cettia/cettia-javascript-client/blob/1.0.0-Beta1/cettia.js#L397-L413
https://github.com/cettia/cettia-javascript-client/blob/1.0.0-Beta1/cettia.js#L775-L797
Since javascript is mono-threaded a simple code like this should be enough on client-side
function () {
if(currentPromise != null){ // define in a closure outside
currentPromise = [..] // call to server which return a promise
currentPromise.then(function(){
currentPromise = null;
});
}
}
I am working on storing some data in the indexedDb.
I have created a method which saves the data into the indexedDb. I have stored exactly 49 records. I am trying to retrieve all of them. I have written the below code for getting the values. No other code except this line exist in my js file.
function crap() {
var indexedDb = window.indexedDB || window.webkitIndexedDB || window.msIndexedDB;
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
var openedDb = indexedDb && indexedDb.open;
var isIndexDbTransactionPossible = window.IDBTransaction || window.webkitIDBTransaction;
if (isIndexDbTransactionPossible) {
isIndexDbTransactionPossible.READ_WRITE = isIndexDbTransactionPossible.READ_WRITE || 'readwrite';
isIndexDbTransactionPossible.READ_ONLY = isIndexDbTransactionPossible.READ_ONLY || 'readonly';
}
var request = indexedDb.open('Offline', DB_VERSION);
request.onupgradeneeded = function(e) {
var db = e.target.result;
if (db.objectStoreNames.contains('tab')) {
db.deleteObjectStore('tab');
}
var store = db.createObjectStore('tab', {keyPath: 'id', autoIncrement: true});
};
request.onsuccess = function(e) {
console.log("DB opened");
var db = e.target.result;
var store= db.transaction('tab', IDBTransaction.READ_ONLY).objectStore('tab');
var cursor = store.openCursor();
cursor.onsuccess = function(event) {
var c = event.target.result;
if (c) {
console.log("New value")
c.continue();
}
};
};
}
I am seeing "New Value" printed 124 times. I am not sure why the cursor.continue() is not returning null after 49th attempt. Any help is much appreciated.
I am positive that this method is not called more than one time. "DB opened" is logged only one.
Just use the getAll function:
var allRecords = store.getAll();
allRecords.onsuccess = function() {
console.log(allRecords.result);
};
Read more in the documentation: Working with IndexedDB
Instead of checking readyState, just check for whether the cursor is defined in your cursor request callback. Here is an example. I modified the names of your variables slightly for clarity.
cursorRequest.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
var value = cursor.value;
console.log('New value:', value);
cursor.continue();
} else {
// Undefined cursor. This means either no objects found,
// or no next object found
// Do not call cursor.continue(); in this else branch because
// there are no more objects over which to iterate.
// Coincidentally, this also means we are done iterating.
console.log('Finished iterating');
}
}
This is probably a noob JavaScript question, but I'm looking to know if my solution to a problem I am having is 'correct'
I have created the following sample application that recreates my error:
Firstly in index.js
var processor = require('./fileProcessor/processor.js');
var container = {
source: "source.txt",
destination: "destination.txt"
};
new processor().process(container);
I create my container object which has the name of the source file and the name of the destination file. This is passed into the process function of the processor:
var fileProcessor = require('./fileProcessor.js');
module.exports = function Processor() {
this.process = function(container) {
var file = new fileProcessor();
if(container.finished === undefined) {
if(container.body === undefined) {
file.read(container, this.process);
} else {
file.write(container, this.process);
}
}
};
};
As you can see this calls the read and write functions passing in the container and the process function as the callback, the fileProcessor looks like this:
var fs = require('fs');
module.exports = function() {
this.read = function(container, callback) {
fs.readFile(container.source, function (err, data) {
if(err) throw err;
container.body = data;
callback(container);
});
};
this.write = function(container, callback) {
fs.writeFile(container.destination, container.body, function(err) {
if(err) {
return console.log(err);
}
container.finished = true;
callback(container);
});
};
};
In simple terms the processor calls file.read, which reads the file and calls back into the process function, which then calls the write function. However at the end of the write function an error is thrown:
callback(container);
^
TypeError: object is not a function
Obviously when passing in this.process to file.write(container, this.process); the this isn't the this I intend it to be!
If I update my processor by adding a processFunction variable:
var fileProcessor = require('./fileProcessor.js');
module.exports = function Processor() {
var processFunction = function(container) {
var file = new fileProcessor();
if(container.finished === undefined) {
if(container.body === undefined) {
file.read(container, processFunction);
} else {
file.write(container, processFunction);
}
}
};
this.process = function(container) {
processFunction(container);
};
};
Everything works fine. Is this a good way to do this or is there a better solution?
I think this is a fine way to do it. There is one possible modification that you might make. Since you are creating a new name in your scope just for the purpose of recursing, you could just name your function and refer to it by its name inside of the function.
module.exports = function Processor() {
this.process = function processFunction(container) {
var file = new fileProcessor();
if(container.finished === undefined) {
if(container.body === undefined) {
file.read(container, processFunction);
} else {
file.write(container, processFunction);
}
}
};
};
Then you can avoid creating a name (processFunction) that will be visible outside the function.
Take a look here for reference:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function#Named_function_expression