Asynchronous method queue in Javascript - javascript

I am attempting to implement an asynchronous method queue in Javascript as seen in this blog post
Here's what I have so far:
function Queue() {
this._methods = [];
this._response = null;
this._flushed = false;
}
(function(Q){
Q.add = function (fn) {
if (this._flushed) fn(this._response);
else this._methods.push(fn);
}
Q.flush = function (response) {
if (this._flushed) return;
this._response = response;
while (this._methods[0]) {
this._methods.shift()(response);
}
this._flushed = true;
}
})(Queue.prototype);
I can't seem to get it to work as advertised, although the code looks correct to me. When I call the flush function I get this._methods is undefined on the line while (this._methods[0]) {.

How are you using it? If you're doing:
var q = new Queue();
q.flush("foo");
...you shouldn't be getting that error, and I'm not: http://jsbin.com/iduji3

Related

BestBuy API won't query in a loop

I am very new to Javascript and I am trying to utilize BestBuy's api to grab data on a specific sku number every 3 seconds.
The call to the api works as expected on its own but when I move the call inside a while loop, I do not get anything and the console.log in the function is not hit.
Not sure where to go from here and I am starting wonder if this api call can only be called by itself.
Would appreciate any help.
var bby = require('bestbuy')('<Actual BestBuy Api key in original code>');
var isSoldOut = true;
while(isSoldOut)
{
console.log("HERE");
bby.products(6465789,{show:'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate'}).then(function(data){
console.log(data);
if (data['onlineAvailability'] == false)
{
isSoldOut = false;
console.log(isSoldOut);
}
});
wait(3000);
}
function wait(ms)
{
var d = new Date();
var d2 = null;
do { d2 = new Date(); }
while(d2-d < ms);
}
Your while loops are constantly blocking the thread, so your callback function can't run.
Instead of the while loops, you can use an interval:
var bby = require('bestbuy')('<Actual BestBuy Api key in original code>');
function check() {
console.log("HERE");
bby.products(6465789,{show:'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate'}).then(function(data){
console.log(data);
if (data['onlineAvailability'] == false)
{
clearInterval(loop);
console.log("In Stock");
}
});
}
const loop = setInterval(check, 3000);
Even cleaner may be using async/await to wait 3 seconds after receiving each response:
var bby = require('bestbuy')('<Actual BestBuy Api key in original code>');
const wait = require('util').promisify(setTimeout);
async function check() {
console.log("HERE");
const data = await bby.products(6465789,{show:'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate'});
console.log(data);
if (data['onlineAvailability'] == false)
{
console.log("In Stock");
return;
}
await wait(3000);
}
check();
There are several points to improve your code:
Use setInterval to execute code in regular interval, instead of using spin lock in a while loop
Use async/await to improve readability
Instead of checking condition == false, simply check !condition
Instead of doing if(!condition){x = false}, simply do x = !condition
Use dot notation instead of bracket notation to access object property if the property name is not variable. This can take advantage of intellisense of IDE if the object shape is properly documented and reduce typo.
const t = setInterval(async () =>{
const data = await bby.products(6465789, { show: 'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate' });
console.log(data);
isSoldOut = !data.onlineAvailability;
console.log(isSoldOut);
if(isSoldOut)
{
clearInterval(t);
}
},3000);

Use of indexedDB returns 'undefined'

I'm trying to use indexedDB.
Some parts of my code works.
In the following example, the first function adds server in my DB, however in Chrome debug console there is an undefined message not related to any line. The server is already added though.
The second function puts records in an array, there is also an undefined message not related to any line.
If I do a console.log(servers); just before return servers; I can see the array content, however if I call the function somewhere else in my code, the returned object is undefined.
var dbName = 'myDBname',
dbServersStoreName = 'servers',
dbVersion = 1,
openDBforCreation = indexedDB.open(dbName, dbVersion);
openDBforCreation.onupgradeneeded = function(e) {
var db = e.target.result;
var objStore = db.createObjectStore(dbServersStoreName, { keyPath: "alias"
});
var index = objStore.createIndex("serversAlias", ["alias"]);
};
function addServerInDB(serverAlias,serverAddress,user,pwd){
var myDB = indexedDB.open(dbName, dbVersion);
myDB.onerror = function() {
var notification = document.querySelector('.mdl-js-snackbar');
notification.MaterialSnackbar.showSnackbar(
{message: 'Error while trying to access internal database'});
}
myDB.onsuccess = function(e) {
var db = e.target.result,
request = db.transaction([dbServersStoreName],
"readwrite").objectStore("servers")
.put({alias:''+serverAlias+'',
address:''+serverAddress+'', login:''+user+'',
passwd:''+pwd+''});
request.onsuccess = function(){
var notification = document.querySelector('.mdl-js-snackbar');
notification.MaterialSnackbar.showSnackbar(
{message: 'Server added'});
}
}
};
function listServersInDB(){
var myDB= indexedDB.open(dbName, dbVersion);
myDB.onerror = function() {
var notification = document.querySelector('.mdl-js-snackbar');
notification.MaterialSnackbar.showSnackbar(
{message: 'Error while trying to access internal database'});
}
myDB.onsuccess = function(e) {
var servers = new Array(),
db = e.target.result,
request = db.transaction(["servers"], "readwrite")
.objectStore("servers")
.openCursor();
request.onsuccess = function(e){
var cursor = e.target.result;
if(cursor){
servers.push(cursor.value);
cursor.continue();
}
return servers;
}
}
};
I do not understand where this undefined comes from and if that is why the listServersInDB() function doesn't work.
You need to learn more about how to write asynchronous Javascript. There are too many errors in your code to even begin reasoning about the problem.
Briefly, don't do this:
function open() {
var openDatabaseRequest = ...;
}
openDatabaseRequest.foo = ...;
Instead, do this:
function open() {
var openDatabaseRequest = ...;
openDatabaseRequest.foo = ...;
}
Next, you don't need to try and open the same database multiple times. Why are you calling indexedDB.open twice? You can open a database to both install it and to start using it immediately. All using the same connection.
Next, I'd advise you don't name the database open request as 'myDB'. This is misleading. This is an IDBRequest object, and more specifically, an IDBOpenRequest object. A request isn't a database.
Next, you cannot return the servers array from the request.onsuccess at the end. For one this returns to nowhere and might be source of undefined. Two this returns every single time the cursor is advanced, so it makes no sense at all to return return servers multiple times. Three is that this returns too early, because it cannot return until all servers enumerated. To properly return you need to wait until all servers listed. This means using an asynchronous code pattern. For example, here is how you would do it with a callback:
function listServers(db, callbackFunction) {
var servers = [];
var tx = db.transaction(...);
var store = tx.objectStore(...);
var request = store.openCursor();
request.onsuccess = function() {
var cursor = request.result;
if(cursor) {
servers.push(cursor.value);
cursor.continue();
}
};
tx.oncomplete = function() {
callbackFunction(servers);
};
return 'Requested servers to be loaded ... eventually callback will happen';
}
function connectAndList() {
var request = indexedDB.open(...);
request.onsuccess = function() {
var db = request.result;
listServers(db, onServersListed);
};
}
function onServersListed(servers) {
console.log('Loaded servers array from db:', servers);
}
When you call a function that does not return a value, it returns undefined. All functions in JavaScript return undefined unless you explicitly return something else.
When you call a function from the devtools console, and that function returns undefined, then the console prints out '-> undefined'. This is an ordinary aspect of using the console.
If you want to get a function that returns the list of servers as an array, well, you cannot. The only way to do that in a pretend sort of way, is to use an 'async' function, together with promises.
async function getServers() {
var db = await new Promise(resolve => {
var request = indexedDB.open(...);
request.onsuccess = () => resolve(request.result);
});
var servers = await new Promise(resolve => {
var tx = db.transaction(...);
var request = tx.objectStore(...).getAll();
request.onsuccess = () => resolve(request.result);
});
return servers;
}
One more edit, if you want to call this from the console, use await getServers();. If you do not use the top-level await in console, then you will get the typical return value of async function which is a Promise object. To turn a promise into its return value you must await it.
Clear and helpfull explanations, Thank you.
I open database multiple times beacause the first time is for checking if DB needs an upgrade and doing something if needed. I'll add 'db.close()' in each functions.
Then, I tried your exemple and the result is the same:
console.log('Loaded servers array from db:', servers); works
but return servers; Don't work.
And in console there is already an undefined without related line :
Screenshot

Trouble with async data in Angular JS application

I am modifying an existing Angular JS application (code can be found here https://github.com/gmathiou/gtfs-manager).
In one file (trips-page-module.js) I have the following code:
$scope.saveTrip = function(itemToSave) {
$scope.computeNewStoptimeSequence();
LoadDataService.saveTrip(itemToSave)
.then(function(data) {
if (data["_id"] != null) {
$scope.tripsForRoutes.push(data);
$scope.active_trip_item = data;
}
})
.then( function (data) {
var dirtyElem = angular.element("ng-dirty");
var toUpdate = [];
for(var i = 0; i <= dirtyElem.length, i++){
toUpdate.push(dirtyElem[i].scope().stoptime);
}
LoadDataService.saveStopTimesForTrip(toUpdate);
})
.then(function(data) {
$scope.editing = false;
});
}
which, basically, creates an array of all the elements that are dirty and have to be updated before passing it to LoadDataService.saveStopTimesForTrip.
Still, when I run it I get that dirtyElem[i] is undefined. I clearly understand this is due to the asynchronous nature of the code.
What I don't get is how can I fix it? How can I wait for all my async function to finish?

How does jQuery do async:false in its $.ajax method?

I have a similar question here, but I thought I'd ask it a different way to cast a wider net. I haven't come across a workable solution yet (that I know of).
I'd like for XCode to issue a JavaScript command and get a return value back from an executeSql callback.
From the research that I've been reading, I can't issue a synchronous executeSql command. The closest I came was trying to Spin Lock until I got the callback. But that hasn't worked yet either. Maybe my spinning isn't giving the callback chance to come back (See code below).
Q: How can jQuery have an async=false argument when it comes to Ajax? Is there something different about XHR than there is about the executeSql command?
Here is my proof-of-concept so far: (Please don't laugh)
// First define any dom elements that are referenced more than once.
var dom = {};
dom.TestID = $('#TestID'); // <input id="TestID">
dom.msg = $('#msg'); // <div id="msg"></div>
window.dbo = openDatabase('POC','1.0','Proof-Of-Concept', 1024*1024); // 1MB
!function($, window, undefined) {
var Variables = {}; // Variables that are to be passed from one function to another.
Variables.Ready = new $.Deferred();
Variables.DropTableDeferred = new $.Deferred();
Variables.CreateTableDeferred = new $.Deferred();
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'drop table Test;',
[],
Variables.DropTableDeferred.resolve()
// ,WebSqlError
);
});
$.when(Variables.DropTableDeferred).done(function() {
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'CREATE TABLE IF NOT EXISTS Test'
+ '(TestID Integer NOT NULL PRIMARY KEY'
+ ',TestSort Int'
+ ');',
[],
Variables.CreateTableDeferred.resolve(),
WebSqlError
);
});
});
$.when(Variables.CreateTableDeferred).done(function() {
for (var i=0;i < 10;i++) {
myFunction(i);
};
Variables.Ready.resolve();
function myFunction(i) {
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'INSERT INTO Test(TestID,TestSort) VALUES(?,?)',
[
i
,i+100000
]
,function() {}
,WebSqlError
)
});
};
});
$.when(Variables.Ready).done(function() {
$('#Save').removeAttr('disabled');
});
}(jQuery, window);
!function($, window, undefined) {
var Variables = {};
$(document).on('click','#Save',function() {
var local = {};
local.result = barcode.Scan(dom.TestID.val());
console.log(local.result);
});
var mySuccess = function(transaction, argument) {
var local = {};
for (local.i=0; local.i < argument.rows.length; local.i++) {
local.qry = argument.rows.item(local.i);
Variables.result = local.qry.TestSort;
}
Variables.Return = true;
};
var myError = function(transaction, argument) {
dom.msg.text(argument.message);
Variables.result = '';
Variables.Return = true;
}
var barcode = {};
barcode.Scan = function(argument) {
var local = {};
Variables.result = '';
Variables.Return = false;
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'SELECT * FROM Test WHERE TestID=?'
,[argument]
,mySuccess
,myError
)
});
for (local.I = 0;local.I < 3; local.I++) { // Try a bunch of times.
if (Variables.Return) break; // Gets set in mySuccess and myError
SpinLock(250);
}
return Variables.result;
}
var SpinLock = function(milliseconds) {
var local = {};
local.StartTime = Date.now();
do {
} while (Date.now() < local.StartTime + milliseconds);
}
function WebSqlError(tx,result) {
if (dom.msg.text()) {
dom.msg.append('<br>');
}
dom.msg.append(result.message);
}
}(jQuery, window);
Is there something different about XHR than there is about the executeSql command?
Kind of.
How can jQuery have an async=false argument when it comes to Ajax?
Ajax, or rather XMLHttpRequest, isn't strictly limited to being asynchronous -- though, as the original acronym suggested, it is preferred.
jQuery.ajax()'s async option is tied to the boolean async argument of xhr.open():
void open(
DOMString method,
DOMString url,
optional boolean async, // <---
optional DOMString user,
optional DOMString password
);
The Web SQL Database spec does also define a Synchronous database API. However, it's only available to implementations of the WorkerUtils interface, defined primarily for Web Workers:
window.dbo = openDatabaseSync('POC','1.0','Proof-Of-Concept', 1024*1024);
var results;
window.dbo.transaction(function (trans) {
results = trans.executeSql('...');
});
If the environment running the script hasn't implemented this interface, then you're stuck with the asynchronous API and returning the result will not be feasible. You can't force blocking/waiting of asynchronous tasks for the reason you suspected:
Maybe my spinning isn't giving the callback chance to come back (See code below).

Javascript global array undefined when in OnSuccess function of PageMethod?

I have multiple user controls loading asynchronously on my page, using pagemethods. Some dropdownlists cause asynchronous callbacks of those usercontrols, and the user controls are reloaded with modified content. We had our reasons for doing it this way.
However, currently our users have to wait for those user controls to load before they can change their selection in the dropdownlists. In an attempt to provide a better user experience, I'm attempting to abort previous, yet-to-be-loaded, requests.
I'm attempting to maintain a global js array of outstanding asynchronous request executors, in order to ensure that only the latest request for each user control is loaded. In other words, I want to cancel previous requests for a specific usercontrol which have yet to load and give priority to the latest request for that usercontrol.
The problem I have is that by the time execution of my OnSuccess function happens, the global array is undefined.
Am I going about this in the wrong way? What is it that I don't know?
Any help would be much appreciated.
This is a pared down example of my code:
var outstanding_requests;
$.fn.remove_or_item = function (val, col) {
var arr1 = $(this);
var arr2 = new Array();
$.each(arr1, function (k, v) {
if (v[col] != val) { arr2.push(v); }
else if (v[1].get_started()) { v[1].abort(); }
});
return arr2;
}
$.fn.find_or_item = function (val, col) {
var item;
var arr1 = $(this);
$.each(arr1, function (k, v) {
if (v[col] == val) { item = v; return false; } return true;
});
return item;
}
function RunMyPageMethod(panelname) {
var request; //current request object
if (outstanding_requests == undefined) { outstanding_requests = new Array(); }
outstanding_requests = $(outstanding_requests).remove_or_item(panelname, 0);
request = PageMethods._staticInstance.LoadUserControl(panelname, PageMethodSuccess, PageMethodFailure);
outstanding_requests.push([panelname, request.get_executor()]);
}
function PageMethodSuccess(result, userContext, methodName) {
var panelname = result.split("|")[0];
//here outstanding_requests is undefined
if($(outstanding_requests).find_or_item(panelname,0))
{
outstanding_requests = $(outstanding_requests).remove_or_item(panelname, 0);
//load usercontrol
}
}
Arrays are pass by reference in js. This has the benefit of allowing me to see the array in the state it was in when I hit the OnSuccess function, not the state it was in when I called the pagemethod. At least I think that's why it works. I needed the reference to the array passed into the OnSuccess function. I ended up doing this with the last two functions shown above, which worked nicely..
function RunMyPageMethod(panelname) {
var request; //current request object
if (outstanding_requests == undefined) { outstanding_requests = new Array(); }
outstanding_requests = $(outstanding_requests).remove_or_item(panelname, 0);
request = PageMethods._staticInstance.LoadUserControl(panelname,function(result,userContext,methodName){ PageMethodSuccess(result,userContext,methodName,outstanding_requests); }, PageMethodFailure);
outstanding_requests.push([panelname, request.get_executor()]);
}
function PageMethodSuccess(result, userContext, methodName, outstanding_requests) {
var panelname = result.split("|")[0];
if($(outstanding_requests).find_or_item(panelname,0))
{
outstanding_requests = $(outstanding_requests).remove_or_item(panelname, 0);
//load usercontrol
}
}

Categories