Wait for openCursor to finish in IndexedDB - javascript

I have this code:
html5DB.indexedDB.addSomething = function(foo) {
var db = html5DB.indexedDB.db;
var trans = db.transaction(["something"], "readwrite");
var store = trans.objectStore("something");
var isExisting = IsAlreadyExist(foo);
// How to wait for that instruction to finish?
if (!isExisting){
var request = store.put({
"text": foo,
"timeStamp" : new Date().getTime()
});
}
};
I'm trying to understand how can I wait for the function IsAlreadyExist to finish. This function opens a cursor to iterate in the object store to determinate if a particular value exist.
Actually, there is no error, but I can't reach the code inside the if (!isExisting) because the value of the variable never changes.
This is the code of that second function:
function IsAlreadyExist(foo){
var db = html5DB.indexedDB.db;
var objectStore = db.transaction("something").objectStore("something");
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
if (cursor.value.text == foo)
return true;
cursor.continue();
}
};
return false;
}
Is there a way to wait for the end of the execution? Or maybe it is not a good way to proceed for checking if a value exist?

As IndexedDB is an asynchronous API you have to wait until operations are completed using callbacks.
First off we need to refactor the IsAlreadyExist method:
function IsAlreadyExist(foo, oncomplete) {
var db = html5DB.indexedDB.db;
var transaction = db.transaction("something");
var objectStore = transaction.objectStore("something");
var exists = false;
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
if (cursor.value.text == foo) {
exists = true;
return;
}
cursor.continue();
}
};
transaction.oncomplete = function () {
oncompleted(exists);
};
}
Now we'll refactor your other method:
html5DB.indexedDB.addSomething = function(foo) {
IsAlreadyExist(foo, function (isExisting) {
if (!isExisting){
var db = html5DB.indexedDB.db;
var trans = db.transaction(["something"], "readwrite");
var store = trans.objectStore("something");
var request = store.put({
"text": foo,
"timeStamp" : new Date().getTime()
});
}
});
};
So here you'll see we're passing the function to execute when the cursor search is done as a callback, which receives an argument of the status.
This can start getting a bit ugly, which is why Promise's are popular in JavaScript. I wrote a wrapper for IndexedDB that uses a Promise-based API called db.js.

Related

How to run a firebase function correctly?

This function will return the value of initiate_session, initiate_session[user_session].events, user_session
before the firebase function runs.
How to run the firebase function first ??
function createSession() {
var user_session = randomCharacters(20) //8iKhA4Aq2!6gZ)890ip#;
var ip = '127.0.0.1';
var initiate_session = new Object();
var session_started = new Date().getTime();
firebase.database().ref(user_session).once('value', function (snapshot) {
if (snapshot.exists()) {
console.log('session exists');
createSession();
} else {
console.log('session not exists')
initiate_session[user_session] = {
ip: ip,
session_started: session_started,
events: {}
};
firebase.database().ref(user_session).set({
ip: ip,
session_started: session_started,
events: {}
});
}
});
console.log('new session', initiate_session);
return [initiate_session, initiate_session[user_session].events, user_session];}
This is because the Firebase function is asynchronous - the code inside the method that gets the snapshot is a callback that is executed after the Firebase read is finished, which may take several seconds. However, as soon as you dispatch the read request via .once(...) your execution flow continues and the return is called.
There are a few possible solutions:
Pass a callback argument to your createSession() method that is called with the values you are trying to return, instead of returning them directly.
Return a promise from your method that resolves to give the values you're trying to return.
Use async/await syntax for your Firebase call. This is covered already in this question: running queries in firebase using async / await
Rough Example of #1
function createSession(onSessionCreated) {
var user_session = "whatever";
var initiate_session = new Object();
firebase.database().ref(user_session).once('value', function (snapshot) {
// do things with the snapshot
onSessionCreated(initiate_session, initiate_session[user_session].events, user_session)
});
}
// usage:
createSession(function (initiate_session, events, user_session) {
// do things with initiate_session, events and user_session
});
Rough Example of #2
function createSession() {
var user_session = "whatever";
var initiate_session = new Object();
firebase.database().ref(user_session).once('value').then(function (snapshot) {
// do things with the snapshot
return [initiate_session, initiate_session[user_session].events, user_session];
});
}
// usage:
createSession().then(function (results) {
// do things with results (i.e. the three elements in the array above)
});
Rough Example of 3
async function createSession() {
var user_session = "whatever";
var initiate_session = new Object();
const snapshot = await firebase.database().ref(user_session).once('value');
// do things with the snapshot
return [initiate_session, initiate_session[user_session].events, user_session];
}
// usage:
const results = await createSession();
If you're new to async/await code it probably won't be the easiest place to start as it may require changes elsewhere in your code, but this article is a good resource if you're keen to learn.

Javascript angular parse queries synchronously (in sequence)

I am trying to use Parse in javascript to make two queries synchronously in sequence:
var innerQuery = new Parse.Query(Attendees);
innerQuery.equalTo("user_id",$localStorage.username);
innerQuery.equalTo("status","confirmed")
innerQuery.equalTo("code_verified",false)
var innerQuerytrue = new Parse.Query(Attendees);
innerQuerytrue.equalTo("user_id",$localStorage.username);
innerQuerytrue.equalTo("status","confirmed")
innerQuerytrue.equalTo("code_verified",true)
innerQuery.find({})
innerQuerytrue.find({})
var eventDetails = []
var queryfalse = new Parse.Query(Events)
queryfalse.matchesKeyInQuery("id_event","event_id",innerQuery);
var querytrue = new Parse.Query(Events)
querytrue.matchesKeyInQuery("id_event","event_id",innerQuerytrue);
queryfalse.find().then(function(results){
for (var i in results) {
var object = results[i];
var eventId = object.get("id_event");
var eventname = object.get("event_name");
var datestart = object.get("date_start");
var location = object.get("location");
var eventimagefile = object.get("event_image");
var eventimageurl = eventimagefile.url();
eventDetails.push({'name':eventname,'eventId':eventId, 'location':location, 'datestart':datestart, 'eventphoto':eventimageurl,'verified':false})
}
}).then(function(){
querytrue.find().then(function(results1){
for (var i in results1) {
var object = results1[i];
var eventId = object.get("id_event");
var eventname = object.get("event_name");
var datestart = object.get("date_start");
var location = object.get("location");
var eventimagefile = object.get("event_image");
var eventimageurl = eventimagefile.url();
eventDetails.push({'name':eventname,'eventId':eventId, 'location':location, 'datestart':datestart, 'eventphoto':eventimageurl,'verified':true})
}
})
}).then(function(){
$scope.events = eventDetails;
})
The second query, queryfalse, is not always executed. I am using promises and not sure if it is the right way to use them.
Just add return to pass the promise returned by querytrue.find().then() down the chain so the next then() won't execute until the promise is resolved. This also keeps your code flatter to avoid deeply nested promises.
queryfalse.find().then(function(results){
// code clipped for brevity
}).then(function(){
return querytrue.find().then(function(results1){
// code clipped for brevity
});
}).then(function(){
$scope.events = eventDetails;
});
Here's a working plunker that demonstrates this: https://plnkr.co/edit/SwPIC1K6yEhEgpIlxoEo?p=preview
You have to move the angular result to the 2nd promise.
var innerQuery = new Parse.Query(Attendees);
innerQuery.equalTo("user_id",$localStorage.username);
innerQuery.equalTo("status","confirmed")
innerQuery.equalTo("code_verified",false)
var innerQuerytrue = new Parse.Query(Attendees);
innerQuerytrue.equalTo("user_id",$localStorage.username);
innerQuerytrue.equalTo("status","confirmed")
innerQuerytrue.equalTo("code_verified",true)
innerQuery.find({})
innerQuerytrue.find({})
var eventDetails = []
var queryfalse = new Parse.Query(Events)
queryfalse.matchesKeyInQuery("id_event","event_id",innerQuery);
var querytrue = new Parse.Query(Events)
querytrue.matchesKeyInQuery("id_event","event_id",innerQuerytrue);
queryfalse.find().then(function(results){
for (var i in results) {
var object = results[i];
var eventId = object.get("id_event");
var eventname = object.get("event_name");
var datestart = object.get("date_start");
var location = object.get("location");
var eventimagefile = object.get("event_image");
var eventimageurl = eventimagefile.url();
eventDetails.push({'name':eventname,'eventId':eventId, 'location':location, 'datestart':datestart, 'eventphoto':eventimageurl,'verified':false})
}
}).then(function(){
querytrue.find().then(function(results1){
for (var i in results1) {
var object = results1[i];
var eventId = object.get("id_event");
var eventname = object.get("event_name");
var datestart = object.get("date_start");
var location = object.get("location");
var eventimagefile = object.get("event_image");
var eventimageurl = eventimagefile.url();
eventDetails.push({'name':eventname,'eventId':eventId, 'location':location, 'datestart':datestart, 'eventphoto':eventimageurl,'verified':true})
}
}).then(function(){
$scope.events = eventDetails;
});
})
querytrue.find() -> returns a promise, and that promise eventually returns a list of results that you're interested in.
.then() sets up some action after a promise, and returns another promise immediately. You can also pass a success handler & error handler function
so, when you say
var p = querytrue.find().then(onSuccess, onError)
p is a promise that will become the return value of the success or failure function, whichever one runs. You can chain another promise by adding another .then()
This chained promise gets the return value of the first promise.
If the first promise success function ran, the second chained success one will also run, likewise with error functions.
If your success handler creates another promise (like your question), you can return that promise from the success handler, and any subsequent chained promises will wait for that promise. Sort of adding a link to the chain.
var p = query1.find();
p.then( function(){
... // runs after query1.find()
var q = query2.find().then(...);
return q;
})
.then( function() {
... // runs after query1.find() and query2.find()
});

ES6 Koa.js run generator function to completion and return asynchronously

Using koa.js, I want to make an API which runs a generator function that runs a long time in the background, but sends a token back to the user immediately.
The user can then use that token to retrieve status of their job later.
'use strict';
var generateToken = function(){
//...
};
var processData = function *(data, token) {
//...
var a = yield analysis(data);
console.log(a) // a is undefined
};
app.post('/process_data', validate, function *(next) {
var token = generateToken();
var that = this;
setTimeout(function() {
for (var i of processData(that.request.body, token)){
continue;
}
});
this.body = "this should return immediately " + token;
return next;
});
Running it within the setTimeout, variable 'a' is not saved. How do I construct this so that processData runs exactly like a normal yield?
You would probably want to have the long running process get handled by a job queue such as Kue
You would queue the job with a http post
then check on the job with a http get
Here is a rough outline of what I think you want to be doing:
var kue = require('kue'),
koa = require('koa'),
route = require('koa-router'),
thunkify = require('thunkify'),
parse = require('co-body'),
co = require('co'),
app = koa(),
jobs = kue.createQueue();
app.use(route(app));
// turn callbacks into thunks for generators
var createJob = thunkify(jobs.create);
var findJob = thunkify(kue.Job.get);
// Process the job here
jobs.process('longProcess', function(job, done){
// do work in here
// call done(err) when completed
// EDIT: if you want to handle job using generators/yield
// you could use a library like co
co(function *(){
var qs = yield doWork(job.data);
done();
}).error(done);
});
// Queue/Start the Job here
app.post('/jobs', function *(){
var body = yield parse(this);
var job = yield createJob('longProcess', body);
this.body = job.id;
});
// Check Status of job here
app.get('/jobs/:token', function *(){
var job = yield findJob(this.params.token);
this.body = job;
// job.status === 'complete' || ...
});
app.listen(3000);
Thanks to Bergi for the solution.
app.post('/process_data', validate, function *(next) {
var token = generateToken();
co(processData(this.request.body, token));
this.body = "this should return immediately " + token;
return next;
});

Howto open IndexedDB just once with jquery promise

I am using Jquery promises to handle opening of an indexedDB to store files and then read and write files to it (am using these for the first time). As any of the functions can be called in any order I always call the function to open the DB first before attempting the operation, the code is as below,
var DatabaseSingleton = (function () {
var openDbPromise = $.Deferred();
var openDb = function() {
var db;
var request = window.indexedDB.open("myDb", 2);
request.onerror = function(event) {
console.error('Error opening indexedDB connection.');
openDbPromise.reject();
}
request.onsuccess = function(event) {
console.log('db opened', request.result);
db = request.result;
db.onerror = function(event) {
console.error("Database error: " + event.target.error.name);
};
openDbPromise.resolve(db);
}
request.onupgradeneeded = function(event) {
console.log('upgrading the idb', event.target.result);
db = event.target.result;
// create a store for the files
db.createObjectStore("fileInfo", { keyPath: "name" }).createIndex("name", "name", { unique: false });
};
return openDbPromise.promise();
};
return {
// retrive a list of all files in the DB
getFilesList: function() {
var filesPromise = $.Deferred();
openDb().then(function(db) {
...
});
return filesPromise.promise();
},
// retrieve metainfo of the file specified by its fileName
getFileinfo: function (fileName) {
var getInfoPromise = $.Deferred();
openDb().then(function(db) {
...
});
return getInfoPromise.promise();
},
}) ();
However with this I notice the 'db opened' being displayed every time any of the functions are called. Is there a better way to make sure that it is only being opened once and then just resolved for succeeding calls?
As it stands, var request = window.indexedDB.open("myDb", 2); etc is executed unconditionally every time openDb() is called.
The most straightforward approach is to introduce an if(...){} clause, to ensure that var request = window.indexedDB.open("myDb", 2); etc is only executed when a successful request (and hence db) has not been established (or is not in the process of being established).
Try this :
var DatabaseSingleton = (function () {
var openDbDeferred;
var openDb = function() {
if(!openDbDeferred || openDbDeferred.isRejected()) {
openDbDeferred = $.Deferred();
var db;
var request = window.indexedDB.open("myDb", 2);
request.onsuccess = function(event) {
console.log('db opened', request.result);
db = request.result;
db.onerror = function(event) {
console.error("Database error: " + event.target.error.name);
};
openDbDeferred.resolve(db);
};
request.onerror = function(event) {
console.error('Error opening indexedDB connection.');
openDbDeferred.reject();
};
request.onupgradeneeded = function(event) {
console.log('upgrading the idb', event.target.result);
db = event.target.result;
// create a store for the files
db.createObjectStore("fileInfo", { keyPath: "name" }).createIndex("name", "name", { unique: false });
};
}
return openDbDeferred.promise();
};
return {
//retrive a list of all files in the DB
getFilesList: function() {
return openDb().then(function(db) {
...
});
},
//retrieve metainfo of the file specified by its fileName
getFileinfo: function(fileName) {
return openDb().then(function(db) {
...
});
}
};
}) ();
If you don't want openDb() to keep trying after previous failure, then change :
if(!openDbDeferred || openDbDeferred.isRejected()) {
to :
if(!openDbDeferred) {
I've written a small jquery plugin (still very alpha) that does this:
https://github.com/ameyms/jquery-indexeddb
And I've tried to keep the API super simple:
//Define and initialize an IndexedDB ...
var db = $.idb({
name:'foobar',
version: 2,
drop: stores_to_be_deleted,
stores:list_of_stores
});
// ... Add objects to a store
db.put(items, 'into_store').done(onsuccess);
//.. And delete objects from a store
db.remove('from_store', conditionFunc).done(onremoval);
//.. And not to forget fetching objects from a store
db.select('from_my_store', conditionFunc).done(function (items){
console.log(items)
});
Hope you like it!
The design is not robust in practice, because you cannot reliably use the database connection. It can be blocked at any time by other connection. Without closing the connection, you will find infrequently that, a promise never resolve due to race condition among connections. On the other hand, if you close the connection, how will consumer know the connection is closed.

Titanium HTTPClient returns too 'fast'

I have the following function:
getTasks: function()
{
var taskRequest = Titanium.Network.createHTTPClient();
var api_url = 'http://myawesomeapi.heroku.com/users/' + Ti.App.Properties.getString("userID") + '/tasks';
var tasks = [];
taskRequest.onload = function() {
var response = JSON.parse(this.responseText),
len = response.length,
i = 0,
t;
for(; i < len; i++)
{
task = response[i];
var newTask = {};
newTask.rowID = i;
newTask.title = task.title;
newTask.description = task.description;
newTask.id = task.id;
newTask.hasChild = true;
tasks.push(newTask);
}
alert(tasks);
}
taskRequest.open('GET', api_url, false);
taskRequest.setRequestHeader('Content-Type', 'application/json');
taskRequest.send();
alert(tasks);
// return tasks;
}
This function is in my controller; I call it in my view when I need to load the data in. However, I wish to return this data so I can assign it to a variable in the view.
Now what happens is that it returns emptiness. The last alert (bottom one) seems to be running too fast and it returns an empty array, while the one that only gets alerted after the onload function is done, contains what I need.
Now my obvious question, how can I get my function to return the array with the data, instead of without?
Putting a timer on it seems hardly the right decision.. Thanks!
"However, I wish to return this data so I can assign it to a variable in the view."
Aside from making the AJAX request synchronous (which you probably don't want), there isn't any way to return the data.
Whatever code relies on the response needs to be called from within the response handler.
Since functions can be passed around, you could have your getTasks method receive a callback function that is invoked and will receive the tasks Array.
getTasks: function( callback ) // receive a callback function
{
var taskRequest = Titanium.Network.createHTTPClient();
var api_url = 'http://myawesomeapi.heroku.com/users/' + Ti.App.Properties.getString("userID") + '/tasks';
taskRequest.onload = function() {
var tasks = [];
// code populating the tasks array
alert(tasks);
callback( tasks ); // invoke the callback
}
taskRequest.open('GET', api_url, false);
taskRequest.setRequestHeader('Content-Type', 'application/json');
taskRequest.send();
}
So you'd use it like this...
myObj.getTasks(function(tasks) {
alert('in the callback');
alert(tasks);
// Any and all code that relies on the response must be
// placed (or invoked from) inside here
some_other_function();
});
function some_other_function() {
// Some more logic that can't run until the tasks have been received.
// You could pass the tasks to this function if needed.
}
You are getting empty alert because when the bottom alert is executed the server response is not available and tasks array is empty.
When the server response comes the tasks array is populated by the code which you have in the onload handler so you see the tasks in the second alert.

Categories

Resources