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);
});
Related
Sample code:
const googleLoadPromise = new Promise(function (resolve, reject) {
google.charts.setOnLoadCallback(function () {
resolve(1);
});
});
googleLoadPromise.then(function () {
// consumer 1 - do something
});
googleLoadPromise.then(function () {
// comsumer 2 - do something else
});
i.e. for the googleLoadPromise, there are two consumers. Is this sort of pattern ok? It seems to work ok - i.e. both consumers get called, and they don't seem to cause problems for each other.
Also, if this is ok, is the order of the running the consumers deterministic (just out of interest, more than anything)?
Yes of course.
You can also just do this if you want to run both functions in sequence:
const googleLoadPromise = new Promise(function (resolve, reject) {
google.charts.setOnLoadCallback(function () {
resolve(1);
});
});
googleLoadPromise
.then(function () {
// consumer 1 - do something
}).then(function () {
// comsumer 2 - do something else
});
Just also remember to handle rejections and catch your promise.
Promises become much easier with async await as well. The below example is more inline with what you wrote:
const googleLoadPromise = new Promise(function (resolve, reject) {
google.charts.setOnLoadCallback(function () {
resolve(1);
});
});
function1 = () => {
// consumer 1 - do something
}
function2 = () => {
// consumer 1 - do something
}
(async function() {
try {
const result = await googleLoadPromise();
function1();
function2();
}
catch( error ) {
console.error( error );
}
}());
Is this sort of pattern ok?
Sure, thats the main usecase of promises.
is the order of the running the consumers deterministic (just out of interest, more than anything)?
Yes, the callback attached first with then gets executed first, however I wouldnt rely on it. If you want to run one callback after another, chain them accordingly.
// ok:
promise
.then(first);
promise
.then(second);
// better if second depends on first:
promise
.then(first)
.then(second);
I'm trying to get promises work for the first time. However, my Promise.all is never firing.
I'm using node.js & express
const promises = [
new Promise( () => {
var query = `...`;
mssql.query(query, function(obj){
finalRes['key1'] = obj.recordset;
return true;
//this works
});
}),
new Promise( () => {
var query = `...`;
mssql.query(query, function(obj){
finalRes['key2'] = obj.recordset;
return true;
//this works
});
}),
...
]
Promise.all(promises).then(() => {
res.send(finalRes);
// this is never firing
});
I have been googling stuff and I can't find a solution. I would appreciate someone point out what I do wrong here.
Cheers
Your promise creation code is wrong, as they never resolve. What you actually should do is fire resolve functions inside your callback-based code. I'd go a bit further - and make all those promises resolve with their results instead of modifying some external value. Like this:
const promises = [
new Promise( (resolve, reject) => {
var query = `...`;
mssql.query(query, function(obj){
resolve({key1:obj.recordset});
});
}),
new Promise( (resolve, reject) => {
var query = `...`;
mssql.query(query, function(obj){
resolve({key2:obj.recordset});
});
}) // ...
];
Promise.all(promises).then(results => {
res.send(Object.assign({}, ...results));
});
Depending on how queries are built, you might go even further - and write a generic query-generator function, which takes query and key as params, and returns a promise. Again, this function should be easy to test.
As a sidenote, your code is overly optimistic, it should also provide error callbacks with reject() invoke to each query.
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));
I'm trying to learn about what the promise is and how to convert callback to promise. While I'm converting my code to promise I got very confused about the ref. I would very appreciate it if you show me how to convert this code as a simple example.
database.ref('/users').on("child_added").then(function(snap){
var subData = snap.val();
database.ref('/subs/' + subData.subid + '/pri/' + snap.key).once("value").then(function(userSnap) {
var userData = userSnap.val();
subData.name = userData.name;
subData.age = userData.age;
database.ref('/subs/' + subData.subid).once("value",function(subDSnap) {
var subDData = subDSnap.val();
subData.type = subDData.type;
database_m.ref('/users/' + snap.key).set(subData);
});
});
});
A Promise is not a replacement for every type of callback; rather it's an abstraction around one particular task that will either succeed once or fail once. The code you're converting looks more like an EventEmitter, where an event can occur multiple times, so replacing .on('child_added', ...) with a Promise implementation is not a good fit.
However, later on, you have a .once(...) call. That's a bit closer to a Promise in that it will only complete once. So if you really wanted to convert that, here's what it could look like:
function get(database, url) {
return new Promise(function (resolve, reject) {
database
.ref(url)
.once('value', resolve)
.once('error', reject);
});
}
database.ref('/users').on("child_added", function(snap) {
var subData = snap.val();
get(database, '/subs/' + subData.subid + '/pri/' + snap.key)
.then(function(userSnap) {
var userData = userSnap.val();
subData.name = userData.name;
subData.age = userData.age;
return get(database, '/subs/' + subData.subid);
})
.then(function(subDSnap) {
var subDData = subDSnap.val();
subData.type = subDData.type;
database_m.ref('/users/' + snap.key).set(subData);
})
.catch(function (err) {
// handle errors
});
});
});
I am not sure I understand the question and if your "on" is returning a promise or just listening, but in your code you have nested '.then', which is not the common way to deal with promises and I am not sure that's what you wanted to achieve here.
You could do (assuming that the on function returns a promise, which I doubt)
database.ref('/users').on("child_added")
.then(function(snap){/* do something with the first part of your snap function*/})
.then (results => {/* do something else, such as your second ref call*/})
.catch(error => {/* manage the error*/})
To learn about promises, there are many examples online but what I really liked is the promise tutorial at google, with this nice snippet that explains it
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
then, once you have the function that returns this promise, you can start doing
.then(...)
.then(...)
.then(...)
.catch(...)
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.