JavaScript Exception Is Lost In Promise Chain - javascript

I am new to JavaScript promises and I am trying to implement them into some PhoneGap code on an Android device. I want to log exceptions and it looks like exceptions are swallowed somewhere. See the sample code below. The exception thrown due to the call to the non-existent function "thiswillfail" does not show anywhere. I commented out the irrelevant code and added code to force the AddRecord promise to be called. The code checks if a record exists and, if not, return the AddRecord promise which is the error is. I am not using any 3rd party libraries. What am I doing wrong?
Edit: If I add another promise in the chain "DoSomethingWithRecord", this promise is called when the expectation is to skip to the catch.
function TestPromiseExceptionHandling() {
var record = null;
var CheckForRecord = function () {
return new Promise(function (resolve, reject) {
//getData(
// function (data) {
var data = "";
if (data != "") {
//record = new Record(data);
record = "existed";
resolve();
}
else return AddRecord();
// },
// function (err) {
// reject(new Error("An error occurred retrieving data, msg=" + err.message));
// });
});
};
var AddRecord = function () {
return new Promise(function (resolve, reject) {
thiswillfail();
//add record
var success = true;
record = "new";
if (success) resolve();
else reject(new Error("add record failed"));
});
};
var DoSomthingWithRecord = function () {
return new Promise(function (resolve, reject) {
alert(record);
resolve();
});
};
try {
CheckForRecord()
.then(DoSomthingWithRecord())
.catch(function (err) { alert(err.message);})
.then(function () { alert("done"); });
} catch (err) {
alert(err.message);
}}

You can't return from the promise constructor, when you do:
else return AddRecord();
Nothing will wait for AddRecord, instead, you want to resolve with AddRecord which will wait for it before resolving the promise:
else resolve(AddRecord());
However, if this is your code you can just return AddRecord() instead of using the promise constructor anyway. The promise constructor (new Promise) is mostly useful for converting non-promise APIs to promises and aren't supposed to be used with already promisified APIs. Use thens instead.

Related

How do catch a reject from a function which contains a promise but does not return it

I have a function main which contains a promise but does not return one. Something like this.
function main() {
var prom = new Promise(function(resolve, reject) {
setTimeout(() => {
console.log("testing");
reject();
}, 2000);
});
return "success";
}
main();
When I run the code, it gives an Uncaught (in promise) DOMException and stops the javascript code. I can't modify the main function at all. How can I catch the rejection?
You can listen for the "unhandledrejection" event.
window.addEventListener("unhandledrejection", e => {
e.preventDefault(); // prevent error message
// handle...
// e.promise is the Promise that was rejected
// e.reason is the object passed to reject()
});

How to wait for a bluebird promise to settle in multiple locations?

I have a situation where a bunch of functions are needing to wait for a promise to settle because it's the init function;
self.init=new Promise(function(resolve){
//do stuff, take awhile
resolve();
});
But, while it's init'ing, the async nature means other functions that depend on it being init are being called. I want those functions to wait for the init to finish, then continue.
I tried doing this inside each function
function doSomethingUseful(){
self.init.reflect().then(function () {
//do functions purpose
});
}
function doSomethingUseless(){
self.init.reflect().then(function () {
//do functions purpose
});
}
But it only works randomly, probably only works if init has settled, and if it hasn't, it just hangs here, weirdly hangs the whole app, despite it being async.
I am trying to replace a former solution that involved intervals and checking a Boolean isInit in each function call.
Is there a bluebird function to do this? Or another way to keep waiting and checking on a promise to see if it is resolved?
The app has this sort of structure in a number of places. Usually around sqlite read/writes. An init to open the database, but while it's opening, the page is loading and it's already trying to read/write to the tables, so those read/writes are forced to wait by using setInterval and repeatedly checking to see if the init has finished.
Here's an example using google analytics.
function Analytics() {
var self = this;
self.ready = ko.observable(false).subscribeTo('application:ready'); //attached to page ready event in jquerymobile and cordova
self.trackerInit = new Promise(function (resolve, reject) {
ko.computed(function () {
if (self.ready()) {
window.ga.startTrackerWithId('id', 1000, resolve, reject);
}
});
});
}
Analytics.prototype.trackSpeed = function (cat, interval, variable, label) {
var self = this;
console.log("speed tracker", cat, interval, variable, label); //this logs
return self.trackerInit.then(function () {
console.log("speed tracker confirm init"); //this never logs, all execution stops including other async code
return new Promise(function (resolve, reject) {
window.ga.trackTiming(cat, interval, variable, label, resolve, reject);
});
}).catch(function (e) {
if (e.message === "send timeout") {
return true; //who cares about timeouts anyways
} else {
throw e;//rethrow it
}
});
};
Function is called within page change event without a return, purely async. Calling it causes all execution to stop.
The ready ko is done like this
self.ready = ko.observable(false).publishOn('application:ready');
var deviceReady = new Promise(function (resolve) {
$(document).on('deviceready', resolve);
});
var pageReady = new Promise(function (resolve) {
$(document).on('pagecreate', resolve);
});
Promise.all([deviceReady, pageReady]).then(function () {
//a couple of page of code and...
self.ready(true);
});
Changing the init like this produces the same result of a hang when checking it's results
self.trackerInit = new Promise(function (resolve, reject) {
console.log("initting");
checker = setInterval(function () {
if (window.ga) {
console.log("ready init");
window.ga.startTrackerWithId('id', 100, function(){
clearInterval(checker);
console.log("init complete");
resolve();
}, reject);
}
}, 1000);
});
They are just promises. Just use then to chain them
function doSomethingUseful() {
// wait for init to finish, then do our stuff
// return the new chained promise in case someone wants to wait on us
return self.init.then(function () {
// do stuff
});
}
function doSomethingUseless() {
// wait for init to finish, then do our stuff
// return the new chained promise in case someone wants to wait on us
return self.init.then(function () {
// do stuff
});
}
// do both of those things and then do something else!
Promise.all([doSomethingUseful(), doSomethingUseless()]).then(function () {
console.log("init is done. And we've done something useful and useless.")
}
Edit:
Based on your additional code, the problem is that if the application is "ready" before your Analytics component is constructed, then you will never receive the "application:ready" (because it came before you subscribed) so your "ready" observable will remain false. According to the postbox docs, you need to pass true as a second argument to subscribeTo so that you'll get the ready value even if it occurred in the past:
ko.observable(false).subscribeTo("application:ready", true)
However, constructing all of these observables and computeds just to feed into a promise is overkill. How about:
self.trackerInit = new Promise(function (resolve, reject) {
const s = ko.postbox.subscribe("application:ready", function (value) {
if (value) {
s.dispose(); // stop listening (prevent memory leak
window.ga.startTrackerWithId('id', 1000, resolve, reject);
}
}, true);
});
You can even turn this into a promise helper:
function whenReady(eventName) {
return new Promise((resolve, reject) => {
const s = ko.postbox.subscribe(eventName, value => {
if (ready) {
s.dispose();
resolve(value);
}
}, true);
});
}
function startGaTracker(id, timeout) {
return new Promise((resolve, reject) => window.ga.startTrackerWithId(id, timeout, resolve, reject);
}
Then you can write:
self.trackerInit = whenReady("application:ready")
.then(() => startGaTracker("id", 100));

Directory check returning false in node.js

I'm having an issue with a Node.js function. I'm fairly certain that it is just an issue with the function being asynchronous but I want to be sure. I am checking to see if a certain path that was entered by the user is valid by checking if it exists.
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
if (fs.statSync(filePath).isDirectory()){
resolve("Valid");
} else {
reject("Invalid");
}
});
}
These are my calls to the function:
files.directoryExists(sourcePath).then((msg) => {
console.log(msg);
}).catch(function(){
console.error("Promise Rejected");
});
files.directoryExists(destPath).then((msg) => {
console.log(msg);
}).catch(function(){
console.error("Promise Rejected");
});
I'm very new to the whole concept of asynchronous programming and promises so this is becoming quite frustrating. Any help would be appreciated.
It's not really the asynchronous thing that's catching you out, although there's a change you can make to improve that.
statSync can throw an exception (if the path doesn't match anything, for instance); you're not handling that, and so when it throws, that gets converted into a rejection. If you looked at the argument you're getting in your catch handler, you'd see the exception that it raises.
The async improvement is that since you're using a Promise, there's no reason to use statSync. Just use stat so you don't tie up the JavaScript thread unnecessarily.
So something along the lines of:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
// If there was an error or it wasn't a directory...
if (err || !data.isDirectory()) {
// ...reject
reject(err || new Error("Not a directory");
} else {
// All good
resolve(data);
}
});
});
};
Of course, you may choose to have it resolve with false if the thing isn't a directory or any of several other choices; this just gets you further along.
For instance, having an error still be a rejection, but resolving with true/false for whether something that exists is a directory; this provides the maximum amount of information to the caller but makes them work a bit harder if all they care about is true/false:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
if (err) {
// Reject on error
reject(err);
} else {
// Return result on success
resolve(data.isDirectory());
}
});
});
};
or making it always resolve, with false if there's no match or there is a match but it's not a directory:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
resolve(!err && data.isDirectory());
});
});
};
Lots of ways for this function to behave, it's up to you.

How to propagate callback promise value on Model.find( obj , callback)?

Ok, I'm fighting hard this problem. I've spent a lot of time on the past week trying to figure out how to make this work. I've learned promises and made some cool stuff - and I'm love with it. But, I can't make this work.
I'm using Mongoose Model.find() method. This methods receives two arguments: an object that will be used to the query and a callback function that will receive (error, data) objects. I'm calling .find and passing findUser function.
UserModel.find(userObj, findUser)
.then(userFound, createUser);
Inside findUser, I'm creating a Promise and resolving or rejecting it.
var findUser = function(err, data) {
var p1 = new Promise( function (resolve, reject) {
if (data.length) {
console.log('Resolved on findUser');
resolve();
} else {
console.log('Rejected on findUser');
reject();
}
});
};
But, whatever happens on findUser, the success callback is always called. On the console, I can see something like this:
Rejected on findUser
Resolved on find
var userFound = function () {
console.log('Resolved on find');
};
var createUser = function () {
console.log('Rejected on find');
}
How could I propagate the promise value from findUser to .find?
You can try something like this. All changes in your code are commented.
// Remove err argument
var findUser = function(data) {
// Instead of creating a new Promise,
// we return a value to resolve
// or throw a value to reject.
if (data.length) {
console.log('Resolved on findUser');
// Resolve this Promise with data.
return data;
} else {
var reason = 'Rejected on findUser';
console.log(reason);
// Reject this Promise with reason.
throw reason;
}
};
// Function gets data passed to it.
var userFound = function(data) {
console.log('Resolved on find');
};
// Function gets reason passed to it.
var createUser = function(reason) {
console.log('Rejected on find');
};
// Promises will chain and propagate errors as necessary.
UserModel.find(userObj)
.then(findUser)
.then(userFound)
.catch(createUser);

Chaining promises with RxJS

I'm new to RxJS and FRP in general. I had the idea of converting an existing promise chain in my ExpressJS application to be an observable for practice. I am aware that this probably isn't the best example but maybe someone can help shed some light.
What I'm trying to do:
I have two promises - prom1 and prom2
I want prom1 to run before prom2
If prom1 sends a reject(err), I want to cancel prom2 before it starts.
I want the error message prom1 returns to be available to the onError method on the observer.
var prom1 = new Promise(function(resolve, reject) {
if (true) {
reject('reason');
}
resolve(true);
});
var prom2 = new Promise(function(resolve, reject) {
resolve(true);
});
// What do I do here? This is what I've tried so far...
var source1 = Rx.Observable.fromPromise(prom1);
var source2 = source1.flatMap(Rx.Observable.fromPromise(prom2));
var subscription = source2.subscribe(
function (result) { console.log('Next: ' + result); },
// I want my error 'reason' to be made available here
function (err) { console.log('Error: ' + err); },
function () { console.log('Completed'); });
If I understood what you are trying to do - you need to create two deferred observables from functions that return promises and concat them:
var shouldFail = false;
function action1() {
return new Promise(function (resolve, reject) {
console.log('start action1');
if (shouldFail) {
reject('reason');
}
resolve(true);
});
}
function action2() {
return new Promise(function (resolve, reject) {
console.log('start action2');
resolve(true);
});
}
var source1 = Rx.Observable.defer(action1);
var source2 = Rx.Observable.defer(action2);
var combination = Rx.Observable.concat(source1, source2);
var logObserver = Rx.Observer.create(
function (result) {
console.log('Next: ' + result);
},
function (err) {
console.log('Error: ' + err);
},
function () {
console.log('Completed');
});
then for normal case:
combination.subscribe(logObserver);
// start action1
// Next: true
// start action2
// Next: true
// Completed
And case where fisrt promise fails:
shouldFail = true;
combination.subscribe(logObserver);
// start action1
// Error: reason
http://jsfiddle.net/cL37tgva/
flatMap turns an Observable of Observables into an Observable. It's used in many examples with Promises because often you have an observable and in the map function you want to create a promise for each "item" the observable emmits. Because every fromPromise call creates a new Observable, that makes it an "observable of observables". flatMap reduces that to a "flat" observable.
In your example you do something different, you turn a single promise into an observable and want to chain it with another observable (also created form a single promise). Concat does what you are looking for, it chains two observables together.
The error case will work as you would expect.
Observable.forkJoin works great here receiving array of other Observables.
Rx.Observable.forkJoin([this.http.get('http://jsonplaceholder.typicode.com/posts'), this.http.get('http://jsonplaceholder.typicode.com/albums')]).subscribe((data) => {
console.log(data);
});

Categories

Resources