Promises not working for IndexedDB in angularjs - javascript

I have an app using IndexedDB. Originally I made prolific use of callbacks, and decided to clean it up using angularjs promises $q.
FAIL.
http://jsfiddle.net/ed4becky/bumm337e/
angular.module("IDBTest", []);
angular.module("IDBTest")
.service("initSvc", ['$q', function ($q) {
var svc = this;
svc.dbVersion = 1;
svc.open = open;
svc.deleteDB = deleteDB;
var idb = window.indexedDB;
function deleteDB() {
return $q(function (resolve, reject) {
idb.webkitGetDatabaseNames().onsuccess =
function (sender, args) {
if (sender.target.result
&& sender.target.result.length > 0) {
var db = sender.target.result[0];
console.log("deleting " + db);
var request = idb.deleteDatabase(db);
request.onsuccess = function () {
console.log('database ' + db + ' deleted.');
resolve();
};
request.onerror = function () {
reject();
};
} else {
console.log("Nothing to delete");
resolve();
};
};
});
}
function open(dbName) {
return $q(function (resolve, reject) {
var request = idb.open(dbName, svc.dbVersion);
request.onupgradeneeded = function (e) {
var db = e.target.result;
console.log("creating new " + db.name);
e.target.transaction.onerror = function (e) {
console.log(e);
};
db.createObjectStore("table1", {
keyPath: "id"
});
db.createObjectStore("table2", {
keyPath: "id"
});
db.createObjectStore("table3", {
keyPath: "id"
});
};
request.onsuccess = function (e) {
console.log('database ' + dbName + ' open.');
svc.db = e.target.result;
resolve();
};
request.onerror = function () {
reject();
};
});
}
}]);
angular.module('IDBTest')
.factory('$exceptionHandler', ['$log', function ($log) {
return function (exception, cause) {
throw exception;
};
}]);
angular.module('IDBTest')
.run(['initSvc', function (initSvc) {
initSvc.deleteDB()
.then(initSvc.open('testDatabase'))
.then(function () {
console.log(initSvc.db.name + ' initialized');
});
}]);
This fiddle shows my expectation that
Any databases created are deleted.
then
A database is open triggering an onupgradeneeded
then
The database is referenced
Unfortunately the then statments seem to get called BEFORE the promises are resolved in the onsuccess methods of the IDB calls.
To recreate, run the jsfiddle with the console open. May have to run it a couple times to get the exception, but it fails most times, because the last then clause is called before the onsuccess on the database open is called.
Any ideas?

I believe the issue is with the Promise Chain. The documentation from Angular is a bit confusing, but it seems as though if the return value of the callback method is a promise, it will resolve with a value; not a promise. Thus, breaking the chain.
From Angular Promise 'Then' Method Documentation:
then(successCallback, errorCallback, notifyCallback) – regardless of
when the promise was or will be resolved or rejected, then calls one
of the success or error callbacks asynchronously as soon as the result
is available. The callbacks are called with a single argument: the
result or rejection reason. Additionally, the notify callback may be
called zero or more times to provide a progress indication, before the
promise is resolved or rejected.
This method returns a new promise which is resolved or rejected via
the return value of the successCallback, errorCallback (unless that
value is a promise, in which case it is resolved with the value which
is resolved in that promise using promise chaining). It also notifies
via the return value of the notifyCallback method. The promise cannot
be resolved or rejected from the notifyCallback method.
I'm able to get it to initialize with this:
angular.module('IDBTest')
.run(['initSvc', function (initSvc) {
initSvc.deleteDB()
.then(function() {
return initSvc.open('testDatabase');
})
.then(function () {
console.log(initSvc.db.name + ' initialized');
});
}]);

Related

catch and finally functions in angular promise wont work when using $q, but standard Promise do work - what im missing?

I'd like to know why this promises implemented with Angular $q (tested on version 1.5.3) won't execute the "catch" neither "finally" promise functions if an error is thrown (it is being catched by the outer try catch in the example below). Whereas if I do the same with the "new Promise()" it will (Im testing this in the latest version of Chrome by the way).
Run the following code where you can inject $q (like a controller) to try it for yourself. You will notice how angular promise outputs the try/catch console log (and never excecutes the finally func.) whereas the standar promise properly catches the error and runs the catch() and finally() promise functions:
var angularPromise = function (data) {
var defered = $q.defer();
var promise = defered.promise;
var emptyvar = null;
if (data == "fail") {
//generate the exception
console.log("code reaches this point");
var fail = emptyvar.fakeproperty;
console.log("code will never reach this point due to the exception");
defered.reject("failed");//neither this...
}
return promise;
}
var standardPromise = function (data) {
return new Promise((resolve, reject) => {
var emptyvar = null;
if (data == "fail") {
//generate the exception
var fail = emptyvar.fakeproperty;
//in this scenario this error is thrown
//and captured by the promise catch()
//just as if I would call reject()
//which is the expected behaviour
}
});
}
try {
angularPromise("fail")
.then(
function (success) {
console.log("angularPromise: oka", success)
}
)
.catch(
function (fail) {
console.log("angularPromise: fail", fail);
}
).finally(
function (fail) {
console.log("angularPromise: 'finally' gets excecuted...");
}
);
} catch (e) {
console.log("angularPromise: exception catched with try/catch and not captured in promise 'catch'. Also 'finally' is never excecuted...");
}
try {
standardPromise("fail")
.then(
function (success) {
console.log(" standardPromise oka", success)
}
)
.catch(
function (fail) {
console.log("standardPromise: catched as expected", fail);
}
).finally(
function () {
console.log("standardPromise: 'finally' gets excecuted...");
}
);
} catch (e) {
console.log("standardPromise exception catched outside promise", e);
}
Wrapping your promise execution in a try/catch is not necessary—Angular $q's catch would catch any errors, so they would not bubble up to the catch in the try/catch.
Essentially, assuming you abstract your business logic out into a service, your service code would need to look something like this:
angular.module('app')
.factory('myService', function ($q) {
function doSomething(shouldSucceed) {
const deferred = $q.defer();
if (shouldSucceed) {
deferred.resolve('I succeeded! 😃');
} else {
deferred.reject('I failed! ☹️');
}
return deferred.promise;
}
return { doSomething };
});
Then, your controller would call that service and handle it accordingly:
angular.module('app')
.controller('MainCtrl', function ($scope, myService) {
$scope.shouldSucceed = function (shouldSucceed) {
myService
.doSomething(shouldSucceed)
.then(alertMessage)
.catch(alertMessage)
.finally(function () {
alert('FINALLY!');
});
};
function alertMessage(message) {
alert(message);
}
});
I've made a simple view that would allow you to click buttons and toggle each message:
<div ng-app="app" ng-controller="MainCtrl">
<button ng-click="shouldSucceed(false)">Fail</button>
<button ng-click="shouldSucceed(true)">Succeed</button>
</div>
I have provided a working example on CodePen of how to implement Angular's $q promises including .then, .catch, and .finally blocks.

Unit Testing of chain of promises with Jasmine

I want to write the unit test for the factory which have lot chain of promises. Below is my code snippet:
angular.module('myServices',[])
.factory( "myService",
['$q','someOtherService1', 'someOtherService2', 'someOtherService3', 'someOtherService4',
function($q, someOtherService1, someOtherService2, someOtherService3, someOtherService4) {
method1{
method2().then(
function(){ someOtherService3.method3();},
function(error){/*log error;*/}
);
return true;
};
var method2 = function(){
var defer = $q.defer();
var chainPromise = null;
angular.forEach(myObject,function(value, key){
if(chainPromise){
chainPromise = chainPromise.then(
function(){return method4(key, value.data);},
function(error){/*log error*/});
}else{
chainPromise = method4(key, value.data);
}
});
chainPromise.then(
function(){defer.resolve();},
function(error){defer.reject(error);}
);
return defer.promise;
};
function method4(arg1, arg2){
var defer = $q.defer();
someOtherService4.method5(
function(data) {defer.resolve();},
function(error) {defer.reject(error);},
[arg1,arg2]
);
return defer.promise;
};
var method6 = function(){
method1();
};
return{
method6:method6,
method4:method4
};
}]);
To test it, I have created spy object for all the services, but mentioning the problematic one
beforeEach( function() {
someOtherService4Spy = jasmine.createSpyObj('someOtherService4', ['method4']);
someOtherService4Spy.method4.andCallFake(
function(successCallback, errorCallback, data) {
// var deferred = $q.defer();
var error = function (errorCallback) { return error;}
var success = function (successCallback) {
deferred.resolve();
return success;
}
return { success: success, error: error};
}
);
module(function($provide) {
$provide.value('someOtherService4', someOtherService4);
});
inject( function(_myService_, $injector, _$rootScope_,_$q_){
myService = _myService_;
$q = _$q_;
$rootScope = _$rootScope_;
deferred = _$q_.defer();
});
});
it("test method6", function() {
myService.method6();
var expected = expected;
$rootScope.$digest();
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});
It is showing error on
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
After debugging I found that it is not waiting for any promise to resolve, so method 1 return true, without even executing method3. I even tried with
someOtherService4Spy.method4.andReturn(function(){return deferred.promise;});
But result remain same.
My question is do I need to resolve multiple times ie for each promise. How can I wait till all the promises are executed.
method1 does not return the promise so how would you know the asynchrounous functions it calls are finished. Instead you should return:
return method2().then(
method6 calls asynchronous functions but again does not return a promise (it returns undefined) so how do you know it is finished? You should return:
return method1();
In a test you should mock $q and have it resolve or reject to a value but I can't think of a reason why you would have a asynchronous function that doesn't return anything since you won't know if it failed and when it's done.
Method 2 could be written in a more stable way because it would currently crash if the magically appearing myObject is empty (either {} or []
var method2 = function(){
var defer = $q.defer();
var keys = Object.keys(myObject);
return keys.reduce(
function(acc,item,index){
return acc.then(
function(){return method4(keys[index],myObject[key].data);},
function(err){console.log("error calling method4:",err,key,myObject[key]);}
)
}
,$q.defer().resolve()
)
};
And try not to have magically appearing variables in your function, this could be a global variable but your code does not show where it comes from and I doubt there is a need for this to be scoped outside your function(s) instead of passed to the function(s).
You can learn more about promises here you should understand why a function returns a promise (functions not block) and how the handlers are put on the queue. This would save you a lot of trouble in the future.
I did below modification to get it working. I was missing the handling of request to method5 due to which it was in hang state. Once I handled all the request to method 5 and provided successCallback (alongwith call to digest()), it started working.
someOtherService4Spy.responseArray = {};
someOtherService4Spy.requests = [];
someOtherService4Spy.Method4.andCallFake( function(successCallback, errorCallback, data){
var request = {data:data, successCallback: successCallback, errorCallback: errorCallback};
someOtherService4Spy.requests.push(request);
var error = function(errorCallback) {
request.errorCallback = errorCallback;
}
var success = function(successCallback) {
request.successCallback = successCallback;
return {error: error};
}
return { success: success, error: error};
});
someOtherService4Spy.flush = function() {
while(someOtherService4Spy.requests.length > 0) {
var cachedRequests = someOtherService4Spy.requests;
someOtherService4Spy.requests = [];
cachedRequests.forEach(function (request) {
if (someOtherService4Spy.responseArray[request.data[1]]) {
request.successCallback(someOtherService4Spy.responseArray[request.data[1]]);
} else {
request.errorCallback(undefined);
}
$rootScope.$digest();
});
}
}
Then I modified my test as :
it("test method6", function() {
myService.method6();
var expected = expected;
var dataDict = {data1:"data1", data2:"data2"};
for (var data in dataDict) {
if (dataDict.hasOwnProperty(data)) {
someOtherService4Spy.responseArray[dataDict[data]] = dataDict[data];
}
}
someOtherService4Spy.flush();
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});
This worked as per my expectation. I was thinking that issue due to chain of promises but when I handled the method5 callback method, it got resolved. I got the idea of flushing of requests as similar thing I was doing for http calls.

error while using the Q library nodeJS

I have the following nodeJS code.I need to get the machines for each service from the redis database. I am using the 'q' library to simplify the callback problem. However I do not get the output.
I am new to node/callbacks/q. Where is my mistake in the code?
I have a controller.js file with the following code
function getMachines(services) {
var machines = Q.fcall(function() {});
services.forEach(function(service) {
var value = function() {
var deferred = Q.defer();
redisDB.readfromRedis(service, function(result) {
deferred.resolve(result);
});
return deferred.promise;
}
});
return machines;
}
testController.js(calling the getMachines function from the controller.js )
var services = ['dashDb22', 'service1', 'service2', 'service3']
var output = controller.getMachines(services)
console.log(output);
RedisDb.js
function readfromRedis(key, callback) {
client.smembers(key, function(error, value) {
if (error) {
throw error;
}
console.log('VALUE IS: = ' + value);
callback(value);
});
}
Your getMachines() doesn't do much, machines is useless and inside your forEach(), you're storing a function you never execute. Your code being simple, you don't really need to use Q, nodejs has a native Promise support.
function getMachines(services) {
// create an array of promises
var myPromises = services.map(function (service) {
// for each service, create a Promise
return new Promise(function (resolve, reject) {
redisDB.readfromRedis(service, function (result) {
resolve(result);
});
});
})
// takes an array of promises and returns a promise for when they've all
// fulfilled (completed successfully) with the values as the result
return Promise.all(myPromises);
}
getMachines(services).then(function (machines) {
// use machines here
});
You could also make readfromRedis() a promise to make it simpler to use.

How to dispose resources in rejected promises with bluebird?

How can I dispose the serial when the promise is rejected?
The dispose function prints error for rejected promises.
Unhandled rejection TypeError: Cannot call method 'isOpen' of
undefined
var pingPort = function(port){
return new promise(function(resolve, reject){
var serial = new com.SerialPort(port.comName, {
baudrate: 19200,
parser: com.parsers.readline(lineEnd)
}, false);
serial.on("data", function(data){
if (data === responseUuid){
resolve(serial);
}
});
serial.open(function(err){
if (err){
//reject(serial)
}
else{
serial.write(pingUuid + lineEnd);
}
});
});
};
var dispose = function(port){
console.log(port.isOpen());
};
var findPort = function(){
com.listAsync().map(function(port){
return pingPort(port).timeout(100).catch(promise.TimeoutError, function(err) {
console.log("Ping timout: " + port.comName);
dispose(port);
}).catch(function(err){
console.log("Ping error: " + port.comName);
dispose(port);
});
}).each(dispose);
}();
This question almost answers itself, in that "promise" and "dispose" together beg you to consider the Promise Disposer Pattern, which is actually nothing more than judicious use of .then() or .finally() with a callback that disposes/closes something that was created/opened earlier.
With that pattern in mind, you will find it simpler to orchestrate disposal in pingPort() rather than findPort().
Assuming the code in the question to be broadly correct, you can chain finally() as follows :
var pingPort = function(port) {
var serial; // outer var, necessary to make `serial` available to .finally().
return new promise(function(resolve, reject) {
serial = new com.SerialPort(port.comName, {
baudrate: 19200,
parser: com.parsers.readline(lineEnd)
}, false);
serial.on('data', function(data) {
if(data === responseUuid) {
resolve(serial); //or resolve(port);
}
});
serial.open(function(err) {
if(err) {
reject('failed to open');
} else {
serial.write(pingUuid + lineEnd);
}
});
})
.finally(function() { // <<<<<<<<<<<
serial.close(); // "dispose".
});
};
Unlike .then, .finally's handler doesn't modify the value/reason, so can be placed in mid-chain without the need to worry about returning a value or rethrowing an error. This point is rather poorly made in the Bluebird documentation.
With disposal in pingPort(), findPort() will simplify as follows :
var findPort = function(){
com.listAsync().map(function(port) {
return pingPort(port).timeout(100)
.catch(promise.TimeoutError, function(err) {
console.log("Ping timout: " + port.comName);
})
.catch(function(err){
console.log("Ping error: " + port.comName);
});
});
}();
Going further, you may want to do more with the array of promises returned by .map(), but that's another issue.

Can I report Q promise progress without creating a deferred?

I'm creating a promise by calling then.
Can I somehow report progress from inside it, or do I have to use Q.defer (which has notify)?
var promise = doSomething().then(function () {
// somehow report progress from here
});
promise.progress(function (p) {
console.log('progress', p);
});
Use deferred.notify()
It's been a while since this question had been asked, the Q library now support it.
var progress = 96;
deferred.notify(progress);
For example:
function doSomething() {
var deferred = Q.defer();
setTimeout(function() {
deferred.notify(10);
},500);
setTimeout(function() {
deferred.notify(40);
},1500);
setTimeout(function() {
deferred.notify(60);
},2500);
setTimeout(function() {
deferred.notify(100);
deferred.resolve();
},3500);
return deferred.promise;
}
doSomething()
.then(
function () {
// Success
console.log('done');
},
function (err) {
// There was an error,
},
function (progress) {
// We get notified of the progress as it is executed
console.log(progress);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/q.js/1.4.1/q.js"></script>
Progress Notification
It's possible for promises to report their progress, e.g. for tasks
that take a long time like a file upload. Not all promises will
implement progress notifications, but for those that do, you can
consume the progress values using a third parameter to then:
return uploadFile()
.then(function () {
// Success uploading the file
}, function (err) {
// There was an error, and we get the reason for error
}, function (progress) {
// We get notified of the upload's progress as it is executed
});
Like fail, Q also provides a shorthand for progress callbacks called
progress:
return uploadFile().progress(function (progress) {
// We get notified of the upload's progress
});
taken from the official readme
I'm not exactly sure what you mean by "creating a promise by calling then". I'm guessing you mean that you're returning a promise with a then defined? I.e,
var iAmAPromise = someOtherPromise.then(doSomething);
If this is the case then you can wrap the doSomething in a callback function with the appropriate notifications. A working example:
var Q = require('q');
function resolver(deferred){
return function(){
deferred.resolve('return value from initial promise');
}
}
function someOtherPromise( ms ) {
var deferred = Q.defer();
setTimeout( resolver(deferred) , ms );
return deferred.promise;
}
function doSomething(data, cb){
console.log('----- doing something with:', data);
var val = "Did something with: " + data;
cb(val);
}
function reportProgress(doSomething, notifierCb){
notifierCb('waiting on it');
return function(someOtherPromiseResponse){
console.log('--- got promise response: ', someOtherPromiseResponse);
notifierCb('got response', someOtherPromiseResponse);
console.log('--- do something with it');
notifierCb('doing something with it');
doSomething(someOtherPromiseResponse, function(val){
notifierCb('done, result was:', val);
});
};
}
function notifier(update, detail){
console.log('Notifier update:', update, detail||"");
}
function logError(err){
console.log('ERROR:', err);
}
var iAmAPromise = someOtherPromise(1000).then(reportProgress(doSomething, notifier)).catch(logError);
console.log(' (Am I a Promise?)', Q.isPromise(iAmAPromise));
I may have misunderstood your question though.
Turns out: no, I can't.
See also why it might not be a good idea after all.

Categories