I'm awful with Async code in Javascript and have been stuck on something for a while now.
I'm working with WebSql and just going through database initialization steps but one of the loops is not executing in the way I expect it to.
$(document).ready(function() {
initdatabase();
});
function initdatabase() {
var db = window.openDatabase("nothing", "1.0", "nothing", 2 * 1024 * 1024);
db.transaction(function(trans) {
trans.executeSql("CREATE TABLE", [], function(trans, result) {
// success
defaultdata(db);
}, function(error) {
// failed
});
});
}
function defaultdata(db) {
db.transaction(function(trans) {
var lo_data = [
{code:"CODE01", desc:"Code 01 Desc"},
{code:"CODE02", desc:"Code 02 Desc"},
{code:"CODE03", desc:"Code 03 Desc"}
];
for(i = 0; i < lo_data.length; i++) {
trans.executeSql("INSERT", [lo_data[i].code, lo_data[i].desc], function(trans, result) {
// success
console.log("INS : " + i);
}, function(error) {
// failed
});
}
console.log("END");
});
}
But the log to indicate the end is executing before the for loop has finished. If I try validate that the data has been inserted I always get fails because the loop hasn't completed the inserts.
Google says that async code should be handled with promises but I can't find examples of promises being used in an instance like this.
Any help would be greatly appreciated
Convert each callback into a promise, and then use Promise.all
const loDataPromises = lo_data.map(({ code, desc }) => {
return new Promise((resolve, reject) => {
trans.executeSql(
"INSERT",
[code, desc],
function(trans, result) {
console.log('success');
resolve();
},
function(error) {
console.log('failed');
reject();
}
);
});
});
Promise.all(loDataPromises)
.then(() => {
console.log('all done');
});
I haven't been able to find any clear code examples on the internet so I wanted to post the working version here as the answer. Hopefully it can benefit someone also trying to understand promises and promise loops.
After a complete overhaul I've managed to get it working in a way that makes sense. I've change it to be executed as a promise chain and then the function with the for loop is utilizing the promise all logic.
$(document).ready(function() {
////
// promise chain
////
console.log("BEGIN");
f_initdatabase().then(function(result) {
return f_defaultdata(result.db);
}).then(function(result) {
console.log("END");
}).catch(function(result) {
// abandon all hope
});
});
////
// single promise usage
////
function f_initdatabase() {
return new Promise(function(resolve, reject) {
console.log(" INIT DB");
var lo_result = {db:null};
var lv_db = window.openDatabase("thenothing", "1.0", "The Nothing DB", 2 * 1024 * 1024);
lv_db.transaction(function(trans) {
trans.executeSql ("create table if not exists dummydata (dd_idno integer primary key, dd_code text not null, dd_desc text not null)", [], function(trans, results) {
console.log(" INIT DB : DONE");
lo_result.db = lv_db;
resolve(lo_result);
}, function(error) {
lo_result.db = null;
reject(lo_result);
});
});
});
}
////
// loop promise all usage
////
function f_defaultdata(lv_db) {
return new Promise(function(resolve, reject) {
console.log(" DEF DATA");
var lo_result = {db:null};
lv_db.transaction(function(trans) {
var la_promises = [];
var lo_data = [
{dd_code:"CODE01", dd_desc:"Code 01 Desc"},
{dd_code:"CODE02", dd_desc:"Code 02 Desc"},
{dd_code:"CODE03", dd_desc:"Code 03 Desc"}
];
for(i = 0; i < lo_data.length; i++) {
console.log(" INS : " + i);
trans.executeSql (" insert into dummydata (dd_code, dd_desc) values (?, ?)", [lo_data[i].dd_code, lo_data[i].dd_desc], function(trans, results) {
la_promises.push(resolve(lo_result));
}, function(error) {
la_promises.push(reject(lo_result));
});
}
Promise.all(la_promises).then(function(results) {
console.log(" DEF DATA : DONE");
lo_result.db = lv_db;
resolve(lo_result);
}).catch(function() {
lo_result.db = null;
reject(lo_result);
});
});
});
}
This gives the output according to the flow needed
BEGIN
INIT DB
INIT DB : DONE
DEF DATA
INS : 0
INS : 1
INS : 2
DEF DATA : DONE
END
Related
I have the following piece of code right now:
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);
recordPerfMetrics: function(url) {
var self = this;
var perf, loadTime, domInteractive, firstPaint;
var perfData = {};
readFile('urls.txt', 'UTF-8').then(function (urls, err) {
if (err) {
return console.log(err);
}
var urls = urls.split("\n");
urls.shift();
urls.forEach(function(url) {
console.log(url);
self.getStats(url).then(function(data) {
data = data[0];
loadTime = (data.loadEventEnd - data.navigationStart)/1000 + ' sec';
firstPaint = data.firstPaint;
domInteractive = (data.domInteractive - data.navigationStart)/1000 + ' sec';
perfData = {
'URL' : url,
'firstPaint' : firstPaint,
'loadTime' : loadTime,
'domInteractive' : domInteractive
};
console.log(perfData);
}).catch(function(error) {
console.log(error);
});
});
// console.log(colors.magenta("Starting to record performance metrics for " + url));
// this.storePerfMetrics();
});
},
getStats: function(url) {
return new Promise(function(resolve, reject){
console.log("Getting data for url: ",url);
browserPerf(url, function(error, data) {
console.log("inside browserPerf", url);
if (!error) {
resolve(data);
} else {
reject(error);
}
}, {
selenium: 'http://localhost:4444/wd/hub',
browsers: ['chrome']
});
});
},
This is basically reading urls from a file and then calling a function browserPerf whose data being returned is in a callback function.
The console.log("Getting data for url: ",url); is in the same order as the urls that are stored in the file,
but the console.log("inside browserPerf", url); is not conjunction as the same and as expected.
I expect the order of the urls to be:
console.log(url);
console.log("Getting data for url: ",url);
console.log("inside browserPerf", url);
But for reason only the first two are executed in order but the third one is fired randomly after all are being read.
Any idea what i am doing wrong here?
Since you are using Bluebird, you can replace your .forEach() loop with Promise.mapSeries() and it will sequentially walk through your array waiting for each async operation to finish before doing the next one. The result will be a promise who's resolved value is an array of results. You also should stop declaring local variables in a higher scope when you have async operations involved. Declare them in the nearest scope practical which, in this case is the scope in which they are used.
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);
recordPerfMetrics: function() {
var self = this;
return readFile('urls.txt', 'UTF-8').then(function (urls) {
var urls = urls.split("\n");
urls.shift();
return Promise.mapSeries(urls, function(url) {
console.log(url);
return self.getStats(url).then(function(data) {
data = data[0];
let loadTime = (data.loadEventEnd - data.navigationStart)/1000 + ' sec';
let firstPaint = data.firstPaint;
let domInteractive = (data.domInteractive - data.navigationStart)/1000 + ' sec';
let perfData = {
'URL' : url,
'firstPaint' : firstPaint,
'loadTime' : loadTime,
'domInteractive' : domInteractive
};
console.log(perfData);
return perfData;
}).catch(function(error) {
console.log(error);
throw error; // keep the promise rejected
});
});
// console.log(colors.magenta("Starting to record performance metrics for " + url));
// this.storePerfMetrics();
});
},
getStats: function(url) {
return new Promise(function(resolve, reject){
console.log("Getting data for url: ",url);
browserPerf(url, function(error, data) {
console.log("inside browserPerf", url);
if (!error) {
resolve(data);
} else {
reject(error);
}
}, {
selenium: 'http://localhost:4444/wd/hub',
browsers: ['chrome']
});
});
},
You would use this like this:
obj.recordPerfMetrics().then(function(results) {
// process results array here (array of perfData objects)
}).catch(function(err) {
// error here
});
Summary of changes:
Return promise from recordPefMetrics so caller can get data
Use Promise.mapSeries() instead of .forEach() for sequential async operations.
Return promise from Promise.mapSeries() so it is chained with prior promise.
Move variable declarations into local scope so there is no change of different async operations stomping on shared variables.
Rethrow .catch() error after logging so the reject propagates
return perfData so it becomes the resolved value and is available in results array.
I'm trying to follow the 'when' example on Parse JavaScript SDK: Parse.Promise
with the following code:
GetFromDB2 = function(id) {
var DB2 = Parse.Object.extend("DB2");
var q = new Parse.Query(DB2);
q.get(id, {
success: function(res) {
return Parse.Promise.as(10);
},
error: function(res, err) {
console.log( err);
}
});
}
GetData = function() {
var DB1 = Parse.Object.extend("DB1");
var query = new Parse.Query(DB1);
query.equalTo("param", "close");
query.find().then(function(results) {
var promises = [];
_.each(results, function(res) {
promises.push(GetFromDB2(res.get("user")));
});
Parse.Promise.when(promises).then(function() {
console.log(arguments); // expect: [10, 10, ...]
})
});
};
The length of the array arguments is correct but not sure why its values are undefined.
As written, GetFromDB2() returns undefined. To deliver a value, it must return either a value or a promise.
At its simplest, to deliver 10, you could write :
function GetFromDB2(id) {
return 10;
}
But to be asynchronous, and still deliver 10, you need to return a promise that will resolve to 10 :
function GetFromDB2(id) {
return Parse.Promise.as(10);
}
Or the .get(id) query you really want :
function GetFromDB2(id) {
var DB2 = Parse.Object.extend('DB2');
var q = new Parse.Query(DB2);
return q.get(id).then(function(res) {
return 10;
});
//omit `.then(...)` entirely to deliver `res`
}
Now GetData() can be written as follows :
function GetData() {
var DB1 = Parse.Object.extend('DB1');
var query = new Parse.Query(DB1);
query.equalTo('param', 'close');
return query.find().then(function(results) {
var promises = _.map(results, function(res) {
return GetFromDB2(res.get('user'));
});
Parse.Promise.when(promises).then(function() {
console.log(arguments); // expect: [10, 10, ...]
}, function(err) {
console.log(err);
return err;
});
});
};
Notes:
promises = _.map(...) is more elegant than _.each(...) plus `promises.push(...).
moving the error handler into GetData() allows a greater range of possible errors to be handled, eg an error arising from query.find().
By returning a promise, GetData()'s caller is also informed of the eventual asynchronous outcome.
I am having trouble extending a Promise inside a .then(). I am trying to perform DB updates in a for-loop and then close the database after all records are processed. However the application exits with process.exit() right away which means that process.exit() was executed even before all db updates were finished. I am pretty sure I am doing something wrong with the nested promise.
var myDB;
function doSomething() {
return MongoClient.connect(DB_CONNECTION).then(function(db) {
myDB = db;
var collection = db.collection(COLLETION_NAME);
for (var i = 0; i < 10; i++) {
promise.then(function{
collection.update({
symbol: items[i].symbol
}, {
$set: {
value: 123
}
}, {
upsert: true
});
});
}
})
}
var promise = doSomething();
promise.then(function(){
console.log("DONE");
myDB.close();
process.exit();
});
It looks like you are getting a promise back from the MongoClient.connect method so why not use that to chain together. I've put a quick sample together below based on your code:
function doSomething(db) {
return new Promise(function(resolve, reject){
var collection = db.collection(COLLETION_NAME);
for (var i = 0; i < 10; i++) {
collection.update({
symbol: items[i].symbol
}, {
$set: {
value: 123
}
}, {
upsert: true
});
}
resolve(db);
})
}
function connectToDB() {
return MongoClient.connect(DB_CONNECTION);
}
function closeDB(db) {
return new Promise(function(resolve, reject){
db.close();
resolve();
});
}
connectToDB().then(function(db){
return doSomething(db);
}).then(function(db){
return closeDB(db);
}).then(function(){
console.log("DONE");
process.exit();
}).catch(function(error){
console.log('Something went wrong: ' + error);
});
Updated code as per #RayonDabre 's suggestion
function doSomething() {
return MongoClient.connect(DB_CONNECTION).then(function(db) {
myDB = db;
var collection = db.collection(COLLECTION_NAME);
var promises = [];
for (var i = 0; i < 10; i++) {
var innerPromise = collection.update({
symbol: items[i].symbol
}, {
$set: {
value: 123
}
}, {
upsert: true
});
promises.push(innerPromise);
}
return Promise.all(promises);
});
}
var promise = doSomething();
promise.then(function(){
console.log("DONE");
myDB.close();
process.exit();
});
I've following two async operations and then final onResult and onFault defined. How can I chain following two async operations getConnection and then select and then finally calling onResult or onFault
Edit need help in promisifying following sequence.
new Promise(this.getConnection)
.then(this.select)
.then(this.onResult)
getConnection: function(resolve, reject) {
console.log('get connection')
if(database = common.model.connections.Sync.getConnection()) {
database.transaction(function(transaction){
resolve(transaction);
});
} else {
reject("Connection Error");
}
},
select: function(transaction) {
console.log('select', transaction)
var self = this;
var query = "SELECT * FROM configuration WHERE key = 'schedule'";
self.transaction.executeSql(query, [], function(transaction, resultSet){self.selectTransactionComplete(transaction, resultSet)}, function(){self.selectTransactionError()});
},
selectTransactionComplete: function(transaction, resultSet) {
console.log('select transaction complete')
if(resultSet.rows.length == 0) {
this.onResult(false);
} else if(new Date().getTime() - new Date(common.Config.getLastSync()).getTime() > resultSet.rows.item(0).value) {
this.onResult(true);
}
},
selectTransactionError: function(error) {console.log(this.constructor.NAME, this.selectSQL); console.log(error);},
onResult: function(data) {
console.log(data);
},
onFault: function(info) {
console.log(info)
}
after trying couple of things, is this how it's supposed to be done? please review
this.getConnection()
.then(this.select, this.getConnectionError)
.then(this.selectTransactionComplete, this.selectTransactionError)
getConnection: function() {
console.log('get connection')
var promise = new Promise(function(resolve, reject){
try {
database = common.model.connections.Sync.getConnection();
database.transaction(function(transaction){
resolve(transaction);
});
} catch(error) {
reject("Connection Error");
}
});
return promise;
},
getConnectionError: function(info) {
console.log("connectionError");
this.onFault();
},
select: function(transaction) {
console.log('select')
var self = this;
var promise = new Promise(function(resolve, reject){
var query = "SELECT * FROM configuration WHERE key = 'schedule'";
transaction.executeSql(query, [], function(transaction, resultSet){resolve(resultSet)}, function(error){reject(error)});
});
return promise;
},
selectTransactionComplete: function(resultSet) {
console.log('selectTransactionComplete')
/*if(resultSet.rows.length == 0) {
this.onResult(false);
} else if(new Date().getTime() - new Date(common.Config.getLastSync()).getTime() > resultSet.rows.item(0).value) {
this.onResult(true);
}*/
},
selectTransactionError: function(error) {
console.log('selectTransactionError')
//console.log(this.constructor.NAME, this.selectSQL);
//console.log(error);
},
onResult: function(data) {
console.log('onResult')
},
onFault: function(info) {
console.log('onFault')
}
If you are using a promise library like q, the way you would go about chaining the promises is as below;
getConnection: function() {
var deferred = Q.Defer();
console.log('get connection')
try {
database = common.model.connections.Sync.getConnection();
database.transaction(function(transaction){
deferred.resolve(transaction);
});
} catch(error) {
deferred.reject("Connection Error");
}
return deferred.promise;
}
when you call you would do something like below;
Q.when(getConnection)
.then(function(result){
// handle success or resolve
}, function(error){
// handle rejection.
};
Also I suggest reading the common js promises specification
The html5 spec for executeSql includes a success callback and a fail callback:
db.transaction(function(tx) {
tx.executeSql('SELECT * FROM MyTable WHERE CategoryField = ?',
[ selectedCategory ],
function (tx, rs) { displayMyResult(rs); },
function (tx, err) { displayMyError(err); } );
});
If I were using jQuery, is there a way to implement this using the new jQuery promise/deferred hotness?
I just wanted to add one more example.
(function () {
// size the database to 3mb.
var dbSize = 3 * 1024 * 1024,
myDb = window.openDatabase('myDb', '1.0', 'My Database', dbSize);
function runQuery() {
return $.Deferred(function (d) {
myDb.transaction(function (tx) {
tx.executeSql("select ? as Name", ['Josh'],
successWrapper(d), failureWrapper(d));
});
});
};
function successWrapper(d) {
return (function (tx, data) {
d.resolve(data)
})
};
function failureWrapper(d) {
return (function (tx, error) {
d.reject(error)
})
};
$.when(runQuery()).done(function (dta) {
alert('Hello ' + dta.rows.item(0).Name + '!');
}).fail(function (err) {
alert('An error has occured.');
console.log(err);
});
})()
Stumbled across this question while looking for something else, but I think I have some template code that will get you started wrapping webSql queries in jQuery Promises.
This is a sample sqlProviderBase to $.extend onto your own provider. I've got an example with a taskProvider and a page that would call to the taskProvider on the page show event. It's pretty sparse, but I hope it helps point others in the right direction for wrapping queries in a promise for better handling.
var sqlProviderBase = {
_executeSql: function (sql, parms) {
parms = parms || [];
var def = new $.Deferred();
// TODO: Write your own getDb(), see http://www.html5rocks.com/en/tutorials/webdatabase/todo/
var db = getDb();
db.transaction(function (tx) {
tx.executeSql(sql, parms,
// On Success
function (itx, results) {
// Resolve with the results and the transaction.
def.resolve(results, itx);
},
// On Error
function (etx, err) {
// Reject with the error and the transaction.
def.reject(err, etx);
});
});
return def.promise();
}
};
var taskProvider = $.extend({}, sqlProviderBase, {
getAllTasks: function() {
return this._executeQuery("select * from Tasks");
}
});
var pageThatGetsTasks = {
show: function() {
taskProvider.getAllTasks()
.then(function(tasksResult) {
for(var i = 0; i < tasksResult.rows.length; i++) {
var task = tasksResult.rows.item(i);
// TODO: Do some crazy stuff with the task...
renderTask(task.Id, task.Description, task.IsComplete);
}
}, function(err, etx) {
alert("Show me your error'd face: ;-[ ");
});
}
};
I've been waiting for an answer, but nothing so far, so I'll take a shot. I can't run this so I apologize for any mistakes.
Are you looking for something like:
function deferredTransaction(db,transaction,transactionFunction(transaction)) {
me=this;
return $.Deferred(function(deferedObject){
db.transaction(transactionFunction(transaction),
function(tx,rs) { me.resolve(tx,rs); },
function(tx,err) { me.reject(tx,err); } );
}).promise();
}
dtx=deferredTransaction(db,tx,function(tx) {
tx.executeSql('SELECT * FROM MyTable WHERE CategoryField = ?',
[ selectedCategory ]);
dtx.then(function (tx, rs) { displayMyResult(rs); },
function (tx, err) { displayMyError(err); } );
I find that wrapping the deferred transaction in a function and returning the promise creates a clean looking and reusable implementation of the deferred/promise pattern.
var selectCategory = function() {
var $d = $.Deferred();
db.transaction(
function(tx) {
tx.executeSql(
"SELECT * FROM MyTable WHERE CategoryField = ?"
, [selectedCategory]
, success
, error
)
}
);
function success(tx, rs) {
$d.resolve(rs);
}
function error(tx, error) {
$d.reject(error);
}
return $d.promise();
};
selectCategory()
.done(function(rs){
displayMyResult(rs);
})
.fail(function(err){
displayMyError(err);
});