Waiting for IDBCursor.onsuccess to complete - javascript

I have an IndexedDB containing properties of various elements on the page. I have an index on one of those properties and I use a key range to get a specific list of results.
var key = IDBKeyRange.bound(10, 20);
var cursor = store.index('property').openCursor(key);
The problem I have is with the cursor.onsuccess function. It seems to execute for each result in the result set. Consequently, I can't execute a callback function once all of the results have been parsed.
cursor.onsuccess = function (e) {
var cursor = e.target.result;
if (cursor) {
if (cursor.value.prop1 > 30 && cursor.value.prop2 < 80) {
// Do stuff with result
someArray.push({
prop1: cursor.value.prop1,
prop2: cursor.value.prop2
}):
}
}
cursor.continue();
};

Safest way to know that your action is finished is to use the transaction on complete event. This event is triggered after the cursor is closed.
transaction.oncomplete = function (event) {
console.log('transaction completed');
};
Also to be sure that no error occurred add event listener to transaction events on error and on abort.
transaction.onerror = function (event) {
console.log('transaction error');
};
transaction.onabort = function (event) {
console.log('transaction abort');
};

As it turns out, cursor.onsuccess fires one last time with e.target.result undefined. You can execute a callback function when this happens:
cursor.onsuccess = function (e) {
var cursor = e.target.result;
if (cursor) {
if (cursor.value.prop1 > 30 && cursor.value.prop2 < 80) {
// Do stuff with result
someArray.push({
prop1: cursor.value.prop1,
prop2: cursor.value.prop2
}):
}
} else {
// Execute code here
console.log('There are ' + someArray.length + ' elements in someArray.');
}
cursor.continue();
};

Related

Manipulating data obtained with indexedDB

Im new with IndexedDB and I can not manipulate the data obtained from indexedDB table.I only need do a search values when a button is pressed, then the event activated with the button starts to work and it has to return many results, which may take a few seconds to return values, so I need to use async / await in the callback function. I think the problem is synchronous because I make the callback function async and the function getData() with which I get the data has the word await with it, even so, I can not work with the data, because when I do the console.log(x) it returns the undefined value.
let db;
let request = window.indexedDB.open("Cities", 1);
request.onerror = function (event) {
console.log("error")
};
request.onsuccess = function (event) {
db = event.target.result;
document.getElementById('search').addEventListener('click', async function () {
let x = await getData();
console.log(x)
})
};
function getData() {
let transaction = db.transaction(["City"], "readwrite");
transaction.oncomplete = function (event) {
document.querySelector('body').innerHTML += '<li>Transaction completed.</li>';
};
transaction.onerror = function () {
document.querySelector('body').innerHTML += '<li>Transaction not opened due to error: ' + transaction.error + '</li>';
};
let objectStore = transaction.objectStore("City");
let objectStoreRequest = objectStore.getAll();
objectStoreRequest.onsuccess = function () {
document.querySelector('body').innerHTML += '<li>Request successful.</li>';
let myRecord;
return myRecord = objectStoreRequest.result;
};
Of course, the console.log(x) is only to check that the data obtained is correct, once that point would come the part of the search but that is another story.
I'm not sure if my problem is with async / await or because I do not get the IndexedDB data correctly. Any help?
EDIT: -- I think I have found a solution, even though I think it is not the best way to solve the problem. I have moved all the code of the function getData() within the function that invokes the event, once the data is obtained I work within the method .onsuccess of objectStoreRequest, thus I avoid having to use async / await, I also continue working on the transaction which has not yet been finalized. If someone knows a cleaner way to make it work or explain to me why the original post code does not work, I would be very grateful.
I attach the code with which I am currently working:
let db;
let request = window.indexedDB.open("Cities", 1);
request.onerror = function (event) {
console.log("error")
};
request.onsuccess = function (event) {
db = event.target.result;
document.getElementById('search').addEventListener('click',function () {
let transaction = db.transaction(["City"], "readwrite");
transaction.oncomplete = function () {
document.querySelector('body').innerHTML += '<li>Transaction completed.</li>';
};
transaction.onerror = function (event) {
document.querySelector('body').innerHTML += '<li>Transaction not opened due to error: ' + transaction.error + '</li>';
};
let objectStore = transaction.objectStore("City");
let objectStoreRequest = objectStore.getAll();
objectStoreRequest.onsuccess = function () {
document.querySelector('body').innerHTML += '<li>Request successful.</li>';
let myRecord;
myRecord = objectStoreRequest.result;
console.log(myRecord)
}
})
};
Anyway it seems like no one have another way for resolve this, so Im go to respond myself this post with this response.
This is the only way I have found to solve the problem, although it seems like a dirty code, I think it could be improved.
let db;
let request = window.indexedDB.open("Cities", 1);
request.onerror = function (event) {
console.log("error")
};
request.onsuccess = function (event) {
db = event.target.result;
document.getElementById('search').addEventListener('click',function () {
let transaction = db.transaction(["City"], "readwrite");
transaction.oncomplete = function () {
document.querySelector('body').innerHTML += '<li>Transaction completed.</li>';
};
transaction.onerror = function (event) {
document.querySelector('body').innerHTML += '<li>Transaction not opened due to error: ' + transaction.error + '</li>';
};
let objectStore = transaction.objectStore("City");
let objectStoreRequest = objectStore.getAll();
objectStoreRequest.onsuccess = function () {
document.querySelector('body').innerHTML += '<li>Request successful.</li>';
let myRecord;
myRecord = objectStoreRequest.result;
console.log(myRecord)
}
})
};

Get variable value, javascript firebase

I have one problem. I am trying to get value from one variable but I can't do this. If somebody can help I will appreciate that. This is my code.
function getInfo() {
var ref = firebase.database().ref("db_storage/");
var info = 0;
ref.on("value", function(snapshot) {
info = snapshot.val().length;
}, function (error) {
console.log("Error: " + error.code);
});
return info;
}
var info = getInfo();
alert(info);
Further to my comment above.
The ref.on("value"...) is an event listener that gets triggered when the 'value' event is dispatched by the database ref. When your code runs it goes (roughly speaking) into getInfo(), attaches the event listener, then proceeds to your last line without waiting for the 'value' event.
To hook things up, pass a callback function as follows.
function getInfo(callback) {
var ref = firebase.database().ref("db_storage/");
ref.on("value", function(snapshot) {
var info = snapshot.val().length;
return callback(info);
}, function (error) {
console.log("Error: " + error.code);
return callback(0);
});
}
getInfo(function(info) {
alert(info);
});

Stopping a function until user presses enter jQuery

I've been working on this for days and I can't seem to find a solution.
I want this script to wait until the user presses the enter key after the first value has been inputted into the field. I want the script to keep doing this every time a value is added, but I can't quite seem to find out how to do this.
$(document).ready(function() {
console.log("script loaded");
var apiKey = "";
var itemImage = $(".title-wrap img");
var itemList = [];
var i = 0;
var addPage = false;
// Run through all images and grab all item ID's.
function scrapeItems() {
itemImage.each(function() {
var grabItemID = $(this).attr("src").match(/\d+/)[0];
var disabled = $(this).closest("li.clearfix").hasClass("disabled");
// Add item number as class for easy reference later.
$(this).addClass("item-" + grabItemID);
// If the item's row has "disabled" class, skip this item.
if (disabled) {
return true;
scrapeItems();
}
// Add item to array.
itemList.push(grabItemID);
});
}
scrapeItems();
// Call the API request function and start gathering all bazaar prices.
function getPricing() {
console.log("script started");
$.each(itemList, function(key, value) {
// Set three second timer per API request.
setTimeout(function() {
// Actual API request.
return $.ajax({
dataType: "json",
url: "https://api.torn.com/market/" + value,
data: {
selections: "bazaar",
key: apiKey
},
// When data is received, run this.
success: function(data) {
console.log(value + " request was successful");
var cheapest = null;
// Run through all results and return the cheapest.
$.each(data["bazaar"], function(key, val) {
var cost = val["cost"];
if (cheapest == null || cost < cheapest) {
cheapest = cost;
}
});
var inputMoney = $(".item-" + value).closest("li.clearfix").find(".input-money:text");
inputMoney.val(cheapest - 1).focus();
// I WANT THE FUNCTION TO WAIT HERE UNTIL THE USER PRESSES ENTER
},
// When data is not received, run this.
error: function() {
console.log(value + " request was NOT successful");
}
});
}, key * 3000);
});
}
function checkPage() {
var i = 0;
var url = window.location.href;
i++
setTimeout(function() {
if (url.indexOf("bazaar.php#/p=add") > 0) {
addPage = true;
addButton();
} else {
checkPage();
}
}, i * 1000);
}
checkPage();
function addButton() {
$("#inventory-container").prepend('<button id="start-button" style="margin-bottom:10px;margin-right:10px;">Run Auto-pricing script</button><p id="s-desc" style="display:inline-block;font-weight:bold;text-transform:uppercase;">Press the enter key after the price has shown up!</p>');
}
$(document).on("click", "#start-button", function() {
getPricing();
});
});
I'm at a complete loss on this one guys, so all help is appreciated!
I think you should break down your code a bit more, and move the "on enter" part of the code into a separate function instead of waiting for user input within that success callback.
e.g in pseudo code, different stages of the scraping
let priceData;
const preProcessPriceData = (data) => {
// do some pre-processing, validate, change data formats etc
// return processed data
};
const processPriceData = (data) => {
// called when price data is ready and user pressed enter
// in other words - script continues here
console.log(priceData, 'or', data);
};
scrapeItems();
// in get prices function - remove event handler
$("#some-input-user-is-pressing-enter-in").offOnEnter(processPriceData);
priceData = null;
getPrices().then((data) => {
priceData = data;
let processedData = preProcessPriceData(data);
// add listener to wait for user input
$("#some-input-user-is-pressing-enter-in").onEnter(() => {
// script continues after user presses enter
processPriceData(processedData);
});
});

nodejs write file frequently

I have a listener to listen for the change of content, once the content modified, it will emit the handler function:
$('#editor').on('onchange', () => changeHandler('...','...'));
function changeHandler(filePath, content){
var ws = fs.createWriteStream(filePath, 'utf8');
ws.write(content);
}
My problem is that the 'onchange' occurs too often, so 'write file' too often handles, it may lost data during the period.
Can someone give any suggestion?
Update
Now I've changed code according the answers below looks like:
this.buffer = null; //used to cache
// once content changed, maybe too often
changeHandler() {
if (this.editor.curOp && this.editor.curOp.command.name) {
var id = $('.nav-items li.active .lk-hosts').attr('data-hosts-id');
var content = this.editor.getValue();
// cache data, not immediately write to file
this.buffer = {id: id, content: content};
}
}
setInterval(()=> {
// means there's data in cache
if (this.buffer !== null) {
let id = this.buffer.id;
let content = this.buffer.content;
// reset cache to null
this.buffer = null;
// write file
this.writeContent(id, content, (err)=> {
})
}
}, 800);
Thanks all answers!
Why not simply build a buffer to collect written text then write to file only when you have a certain number of writes:
$('#editor').on('onchange', () => changeHandler('...','...'));
var writeBuffer = ''; // can also make this an array
var writeBufferSize = 0;
var filePath = 'path_to_file';
var ws = fs.createWriteStream(filePath, 'utf8');
function changeHandler(content){
if (writeBufferSize == SOME_THRESHOLD) {
ws.write(writeBuffer);
writeBuffer = '';
writeBufferSize = 0;
} else {
writeBuffer += content + '\n';
writeBufferSize++;
}
}
If you choose a write buffer threshold that's too big, you might want to delegate the write to some worker thread to be done in parallel, and in this case you can create another temporary write buffer to fill out while the original is being written, then switch the two.
This sample below shows how to make debounced event handling, although it's not node.js code it's same in concept.
// eventEmitter variable to use
var emitter = new EventEmitter();
// dom element change event
$('#editor').on('input', function(event) {
emitter.emit('changeEvent', event.target.value);
});
// event listener, which debounces change event of input
emitter.on('changeEvent', debounce(function(data) {
writeFile('li', data);
}, 1000)); // <== debounce for 1second
// sample emitter, for demo
// we don't have access to nodejs EventEmitter class in Stackoverflow
// don't use in production
function EventEmitter() {
var callbacks = [];
return {
on: function(eventName, fn) {
callbacks.push({
eventName: eventName,
callback: fn
})
},
emit: function(eventName, payload) {
var fn = callbacks.find(function(item) {
return item.eventName === eventName;
});
if (fn) {
fn.callback(payload);
}
}
}
}
// simple logger for demo purpose
// emulates write file
function writeFile(name, content) {
var $elem = $(document.createElement(name));
$elem.text(content);
$('#logger').append($elem);
}
// throttle function - reduces fn call with timeout
// credits: https://remysharp.com/2010/07/21/throttling-function-calls
function debounce(fn, delay) {
var timer = null;
return function() {
var context = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="editor" placeholder="Enter text, this will emit change event"></textarea>
<p>
Notice the 1sec throttle (write something, pause for 1sec, write again)
</p>
<ul id="logger"></ul>
The debounce function can be also used on textarea change event
// debounce emitting
$('#editor').on('input', debounce(function(event) {
emitter.emit('changeEvent', event.target.value);
}, 1000));
// write file when received event without debounce
emitter.on('changeEvent', function(data){
logElement('li', data);
});
The Underscore library has _.throttle() and _.debounce() functions.

How to set callback order invocation same as target function invocation

I have a problem in my project.
To describe this issue I have wrote simplified code snippet:
function waitFor(fnReady, fnCallback) {
var check = function() {
if (fnReady()) {
fnCallback();
}
else {
setTimeout(check, 100); // wait another 100ms, and try again
}
};
check();
}
var result = 0;
var flag = true;
function ajaxRequest() {
setTimeout(
function() { flag = false;
console.log('ping');
},3000
);
}
function ajaxRequestHandler() {
setTimeout(
function() { flag = true;
console.log('pong');
}, 200
);
}
for(var i =0;i<10; i++){
waitFor(function() { return flag; }, ajaxRequest);
waitFor(function() { return !flag; }, ajaxRequestHandler);
}
it returns:
ping - 10 times
pong - 10 times
desired result:
ping
3 second timeout
ping
---------------------
ping
3 second timeout
pong
--------------------
.....
Can you help correct my code?
UPDATE
Actual problem:
I have a google map.
I have a lot of places when I should to redraw it.
For application logic very important that If I send
request1
request2
request3
request4
I should handle responses in the this order
handle response of request1
handle response of request2
handle response of request3
handle response of request4
Problem that I don't know order of requests.
In different places of file I see following code rows:
google.maps.event.addListener(searchBox, 'bounds_changed', renderTerminalsOnMapAndFitBounds);
...
$.getJSON('getAllTerminals.json', renderTerminalsOnMapAndFitBounds);
.....
$.getJSON('getAllTerminalsInsideRectangle.json', renderTerminalsOnMapAndFitBounds);
...
$.getJSON('getAllTerminalsInsideCircle.json', renderTerminalsOnMapAndFitBounds);
...
$.getJSON('getBigTerminals.json', renderTerminalsOnMapAndFitBounds);
........
renderTerminalsOnMapAndFitBounds method sends request to server and in succes alternative render result on map. But this event happens very often
Try this pattern
var map = "abcdefghi".split("");
var responses = []; // collect responses
$.ajaxSetup({
beforeSend : function(jqxhr, settings) {
jqxhr.id = Number(settings.data.split(/id=/)[1]); // add `id` to `request`
console.log(settings.data.split(/id=/)[1]);
}
});
var request = function(id, data) {
// append `id` to `id` data
return $.post("/echo/json/", {json:JSON.stringify([data]), id:id})
};
$.each(map, function(k, v) {
setTimeout(function() {
request(k + 1, v)
.done(function(data) {
// do stuff at each response
console.log(data); // note return values
})
.always(function(data, textStatus, jqxhr) {
// do stuff at each response
responses.push([jqxhr.id, data[0]]);
// do stuff when all requests completed , results items in `responses`
if (responses.length === map.length) {
responses.sort(); // sort `responses` based on `id`
// do stuff with `responses`
console.log(responses);
}
});
},1 + Math.random() * 1000) // async
});
jsfiddle http://jsfiddle.net/guest271314/g254bbjg/
my variant:
var index = 0;
// callback function
function tryMe (param1) {
waitFor(function(){return param1 == index},
function(){console.log(param1);
index++;
}
)
}
// callback executer
function callbackTester (callback,i) {
setTimeout( function(){callback(i);}, 20000 - i*1000);
}
// test function
for(var i=0 ; i<10 ; i++){
callbackTester ( tryMe,i );
}
function waitFor(fnReady, fnCallback) {
var check = function() {
if (fnReady()) {
fnCallback();
}
else {
setTimeout(check, 100); // wait another 100ms, and try again
}
};
check();
}
http://jsfiddle.net/x061dx75/17/
I personally would use promises for this, but you've said no promises (not sure why), so here's a generic sequencer algorithm in plain javascript (tested in the jsFiddle linked below):
function sequence(fn) {
// initialize sequence data upon first use
if (typeof sequence.low === "undefined") {
sequence.low = sequence.high = 0;
sequence.results = {};
}
// save id in local variable so we can reference it in the closure from the function below
var id = sequence.high;
// advance to next sequence number
++sequence.high;
// initialize the result value for this sequence callback
sequence.results[id] = {fn: fn, args: [], ready: false, context: null};
return function(/* args */) {
// save args and context and mark it ready
var args = Array.prototype.slice.call(arguments, 0);
// get the results object for this callback and save info in it
var thisResult = sequence.results[id];
thisResult.args = args;
thisResult.context = this;
thisResult.ready = true;
// now process any requests in order that are ready
for (var i = sequence.low; i < sequence.high; i++) {
var result = sequence.results[i];
// if this one is ready, process it
if (result.ready) {
// increment counter past this result
++sequence.low;
// remove this stored result
delete sequence.results[i];
// process this result
result.fn.apply(result.context, result.args);
} else {
// if this one not ready, then nothing to do yet
break;
}
}
};
}
// your usage:
google.maps.event.addListener(searchBox, 'bounds_changed', sequence(renderTerminalsOnMapAndFitBounds));
...
$.getJSON('getAllTerminals.json', sequence(renderTerminalsOnMapAndFitBounds));
.....
$.getJSON('getAllTerminalsInsideRectangle.json', sequence(renderTerminalsOnMapAndFitBounds));
...
$.getJSON('getAllTerminalsInsideCircle.json', sequence(renderTerminalsOnMapAndFitBounds));
...
$.getJSON('getBigTerminals.json', sequence(renderTerminalsOnMapAndFitBounds));
........
Working demo: http://jsfiddle.net/jfriend00/aqugm1fs/
Conceptually, what this does is as follows:
Pass a substitute completion handler in place of the normal completion callback.
This substitute function marks each response with a sequence id and saved the original completion handler.
If a response comes back while another response with a lower sequence id is still pending, then the result is just stored and saved for later.
As each response comes in, it processes as many responses in sequence as are ready
Note: while all the examples you have use the same callback function, this will work with any callback function so it would work with a mix of different types of operations.

Categories