I am not sure how correct I am on this so if any experts are able to correct me on this it would also be appreciated. My current understanding is that observables are lazy and do not produce values until subscribed to. If an error occurs the observable sends no more values. In a lot of cases this is not what is wanted.
In the code sample below I am getting the weather for perth and london and if an error occurs returning an object indicating the error has occured. This pretty much means that the error block on the subscriber will not get called but the success will and I will have to look at whether it failed and change logic. Is this the best way to do this????
Also does the zip operator wait for all obs to produce a value regardless of ordering and return when a single value from all has been produced?
Code as below:
window.application = application || {};
window.application.stocks = (function(){
function getWeather(city, country){
return $.ajaxAsObservable({
url: 'http://api.openweathermap.org/data/2.5/weather?q=' + city + ',' + country,
dataType: 'jsonp'
}).map(function(v){
return {
city : city,
country : country,
temperature : v.data.main.temp
};
});
}
var requests = Rx.Observable
.interval(500)
.timestamp()
.map(function(v){
var perth = getWeather('perth','au');
var london = getWeather('london','uk');
return Rx.Observable.zip(perth,london, function(a,b){
return {
a : a,
b : b,
timestamp : v.timestamp,
failure : false,
};
}).retry(3)
.catch(Rx.Observable.return({ failure: true }));
})
.concatAll();
var selector = $('#weather');
var subscriber = requests.forEach(
function(data){
if(data.failure){
$('<li>Failure in receiving the temperature</li>').appendTo(selector);
return;
}
$('<li> at: ' + data.timestamp + ' for: ' + data.a.city + ' - ' + data.a.temperature + '</li>').appendTo(selector);
$('<li> at: ' + data.timestamp + ' for: ' + data.b.city + ' - ' + data.b.temperature + '</li>').appendTo(selector);
},
function(error){
selector.text('');
$('<li>Error: ' + error + '</li>').appendTo('#weather');
},
function(){
}
);
});
Any suggestions and tips is greatly appreciated.
Thanks in advance.
Blair.
You probably should replace .forEach() with .subscribe() since one is an alias of the other, and .subscribe() is more common and more explicit in this case.
subscribe() can accept three functions as arguments onNext (first function), onError (second function, optional), onComplete (third function, optional). Hence, you probably will achieve what you want by just providing a second function in the subscribe(), to handle errors. Then you don't need the .catch() operator to insert a flag-like event.
Also, for your specific use case, where perth and london Observables are conceptually independent (they have no dependencies with one another), you want to use .combineLatest() instead of .zip(). See this answer for details on the difference between the two.
Interesting as this post says that you do need to flag errors so that the steam does not complete.
Idiomatic way to recover from stream onError
From my current understanding and testing of RX if an error is thrown during process the onError handler of the subscription will get called but it will complete the stream.
Meaning that the subscriber will receive no further messages.
Related
I am trying to push weather data into Google Analytics. (I am following a tutorial online; I am not proficient in Javascript but I know Python.) I have set up datalayer variables, tags and triggers, but I have a custom HTML tag that calls Openweathermap API, and pushes all this data to the datalayer, so my tag can then take this information and fire back to Google Analytics.
Could someone please have a look at this code and tell me why I get a "Unexpected Token on Line 28 ({)" error?
<script>
(function() {
var fetchWeatherData = function(longitude, latitude) {
// Open Weather Map
var owmAppKey = '<baeb0853a54bef1870ecdd0345bb0f5e>';
jQuery.getJSON('https://api.openweathermap.org/data/2.5/weather?lat=' + latitude + '&lon=' + longitude + '&units=metric&APPID=' + owmAppKey)
.done(function(data) {
window.dataLayer.push({
event: 'weatherDone',
weather: data.weather[0].main,
temperature: data.main.temp.toFixed(0) + ' °C'
});
}).fail(function(jq, status, msg) {
console.log('Weather request failed: ' + status + ' - ' + msg);
});
};
var geoLocateUser = function() {
$.getJSON('//extreme-ip-lookup.com/json/')
.done(function(data) {
fetchWeatherData(data.lon, data.lat);
}).fail(function(status, msg) {
console.log('IP request failed: ' + status + ' - ' + msg);
});
};
if (typeof {{Session alive}} === 'undefined') {
geoLocateUser();
}
// Reset "session" cookie with a 30-minute expiration
var d = new Date();
d.setTime(d.getTime()+1800000);
var expires = "expires="+d.toGMTString();
document.cookie = "session=1; "+expires+"; path=/";
})();
</script>
I am guessing this is a really basic Syntax error that is easy to fix, but I am not proficient with Javascript and cannot figure this out.
Many thanks!
A few things:
You're getting an error because typeof {{...}} is improper syntax.
Also, Session alive isn't anything. If it's a variable it should be one word like Session_alive or Session-alive or sessionAlive.
Also, double curly braces {{...}}(moustache template) are usually used in some JS frameworks but are not a feature of vanilla JS.
Single curly braces indicate an object (ex: {a: 'one', b: 'two'}.
If sessionAlive was a variable of some kind and you wished to verify if it was an object you'd write typeof sessionAlive.
But if you're checking to see if the value Session is alive then you'd write a conditional such as if (Session === 'alive') ...,
or check if Session is undefined such as if (Session === undefined) ...
Can you check the "Session alive" tag is properly set up in Google Tag Manager?
I am trying to get the value of a queried ID called from another class however when I call the function it gives me a promise chain and not the value I am looking for.
The method in the class 'Helper' is below
function querySF() {
var conn = new jsforce.Connection({
// you can change loginUrl to connect to sandbox or prerelease env.
loginUrl: 'https://www.salesforce.com'
});
return conn.login('someusername', 'password')
.then(function(userInfo) {
// Now you can get the access token and instance URL information.
// Save them to establish connection next time.
console.log(conn.accessToken);
console.log(conn.instanceUrl);
// logged in user property
console.log("User ID: " + userInfo.id);
console.log("Org ID: " + userInfo.organizationId);
// ...
return conn.query("SELECT Id FROM some place Where name = 'some name'")
})
.then(function(result) {
console.log("total : " + result.totalSize);
console.log("fetched : " + result.records.length);
// is returning the id
console.log(result.records[0].Id);
return result.records[0].Id; // can see the id here when debugging
})
.catch(function(err) {
console.error(err);
});
}
I am exporting the module like this at the bottom of the class:
exports.querySF = querySF();
The other class called 'BookingEvent' calls the method like this: var theId = Helper.querySF; and it returns a promise, I have printed the promise to console console.log(Helper.querySF); to see the results:
Promise {
_45: 0,
_81: 1,
_65: 'a0L46111001LyvsEAC', // This is the value I need
_54: null,
then: [Function],
stream: [Function: createRequest] }
It was thought that I should be able to just use
helpers.querySF().then(function(value){
console.log(value);
})
and be able to get the value however I am getting this error:
Failed: helpers.querySF is not a function
I am quite new to promises and no one at my company can seem to solve the issue. I have research many different ways of resolving promises however they do not work and I also do not understand. Could someone help me resolve this promise so the id accessible whenever I call this method, which will be multiple times with different queries I will be sending in.
If the promises are not related each other, you may find better Promise.all() here instead of chaining promises in this way. Then resolve all of them. If actually you get the name of the second query from the first promise, actually you need to chain them.
Then you are missing a catch in order to catch errors for the first promise.
And maybe a refactor using function may help the code to look better.
return conn.login('someusername', 'password')
.then(elaborateUserInfo)
.catch(catchErrors)
.then(elaborateResults)
.catch(catchErrors);
function elaborateUserInfo(userInfo) {
// Now you can get the access token and instance URL information.
// Save them to establish connection next time.
console.log(conn.accessToken);
console.log(conn.instanceUrl);
// logged in user property
console.log("User ID: " + userInfo.id);
console.log("Org ID: " + userInfo.organizationId);
// ...
return conn.query("SELECT Id FROM some place Where name = 'some name'");
}
function elaborateResults(result) {
console.log("total : " + result.totalSize);
console.log("fetched : " + result.records.length);
// is returning the id
console.log(result.records[0].Id);
return result.records[0].Id; // can see the id here when debugging
}
function catchErrors(err) {
console.log(err);
}
It looks better, doesn't it?
Instead, the only reason for this error Failed: helpers.querySF is not a function is that you don't have that method in your object. Are you sure you really exported it in order to be visible outside your module?
So,I am trying to use the twitch API:
https://codepen.io/sterg/pen/yJmzrN
If you check my codepen page you'll see that each time I refresh the page the status order changes and I can't figure out why is this happening.
Here is my javascript:
$(document).ready(function(){
var ur="";
var tw=["freecodecamp","nightblue3","imaqtpie","bunnyfufuu","mushisgosu","tsm_dyrus","esl_sc2"];
var j=0;
for(var i=0;i<tw.length;i++){
ur="https://api.twitch.tv/kraken/streams/"+tw[i];
$.getJSON(ur,function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> "+tw[j]+"<p>"+""+"</p></li>");
if(json.stream==null){
$(".stat").append("<li>"+"Offline"+"</li>");
}
else{
$(".stat").append("<li>"+json.stream.game+"</li>");
}
j++;
})
}
});
$.getJSON() works asynchronously. The JSON won't be returned until the results come back. The API can return in different orders than the requests were made, so you have to handle this.
One way to do this is use the promise API, along with $.when() to bundle up all requests as one big promise, which will succeed or fail as one whole block. This also ensures that the response data is returned to your code in the expected order.
Try this:
var channelIds = ['freecodecamp', 'nightblue3', 'imaqtpie', 'bunnyfufuu', 'mushisgosu', 'tsm_dyrus', 'esl_sc2'];
$(function () {
$.when.apply(
$,
$.map(channelIds, function (channelId) {
return $.getJSON(
'https://api.twitch.tv/kraken/streams/' + encodeURIComponent(channelId)
).then(function (res) {
return {
channelId: channelId,
stream: res.stream
}
});
})
).then(function () {
console.log(arguments);
var $playersBody = $('table.players tbody');
$.each(arguments, function (index, data) {
$playersBody.append(
$('<tr>').append([
$('<td>'),
$('<td>').append(
$('<a>')
.text(data.channelId)
.attr('href', 'https://www.twitch.tv/' + encodeURIComponent(data.channelId))
),
$('<td>').text(data.stream ? data.stream.game : 'Offline')
])
)
})
})
});
https://codepen.io/anon/pen/KrOxwo
Here, I'm using $.when.apply() to use $.when with an array, rather than list of parameters. Next, I'm using $.map() to convert the array of channel IDs into an array of promises for each ID. After that, I have a simple helper function with handles the normal response (res), pulls out the relevant stream data, while attaching the channelId for use later on. (Without this, we would have to go back to the original array to get the ID. You can do this, but in my opinion, that isn't the best practice. I'd much prefer to keep the data with the response so that later refactoring is less likely to break something. This is a matter of preference.)
Next, I have a .then() handler which takes all of the data and loops through them. This data is returned as arguments to the function, so I simply use $.each() to iterate over each argument rather than having to name them out.
I made some changes in how I'm handling the HTML as well. You'll note that I'm using $.text() and $.attr() to set the dynamic values. This ensures that your HTML is valid (as you're not really using HTML for the dynamic bit at all). Otherwise, someone might have the username of <script src="somethingEvil.js"></script> and it'd run on your page. This avoids that problem entirely.
It looks like you're appending the "Display Name" in the same order every time you refresh, by using the j counter variable.
However, you're appending the "Status" as each request returns. Since these HTTP requests are asynchronous, the order in which they are appended to the document will vary each time you reload the page.
If you want the statuses to remain in the same order (matching the order of the Display Names), you'll need to store the response data from each API call as they return, and order it yourself before appending it to the body.
At first, I changed the last else condition (the one that prints out the streamed game) as $(".stat").append("<li>"+jtw[j]+": "+json.stream.game+"</li>"); - it was identical in meaning to what you tried to achieve, yet produced the same error.
There's a discrepancy in the list you've created and the data you receive. They are not directly associated.
It is a preferred way to use $(".stat").append("<li>"+json.stream._links.self+": "+json.stream.game+"</li>");, you may even get the name of the user with regex or substr in the worst case.
As long as you don't run separate loops for uploading the columns "DisplayName" and "Status", you might even be able to separate them, in case you do not desire to write them into the same line, as my example does.
Whatever way you're choosing, in the end, the problem is that the "Status" column's order of uploading is not identical to the one you're doing in "Status Name".
This code will not preserve the order, but will preserve which array entry is being processed
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
if (json.stream == null) {
$(".stat").append("<li>" + "Offline" + "</li>");
} else {
$(".stat").append("<li>" + json.stream.game + "</li>");
}
})
}(i));
}
});
This code will preserve the order fully - the layout needs tweaking though
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
var name = $(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
var stat = $(".stat").append("<li></li>")[0].lastElementChild;
console.log(stat);
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
if (json.stream == null) {
$(stat).text("Offline");
} else {
$(stat).text(json.stream.game);
}
}).then(function(e) {
console.log(e);
}, function(e) {
console.error(e);
});
}(i));
}
});
I cannot find a way to get my find function to work.
I would like to find all billIds from the bills collection and then check for each billId inside the transaction DB.
The problem is, because of the aSynch - I reckon, the id doesn't update on my loop.
Here is the code:
Bills.find({type: bill_type, endDate: {"$gte" : new Date(year + "-" + month + "-1")}}).find(function(err, bills){
if(err)
res.send(err);
details.bills = bills;
for(key in bills){
var billId = bills[key]._id;
console.log("BillId: " + billId); // Here the ids are unique (which is what I want)
Transactions.find({billId : billId}, function(err, transactions){
if (err) {
console.log('error: '+ err)
} else {
console.log("Transaction BillId: " + billId); // Here I get always the same ID - which is not quiet what I need.
}
});
//console.log(transactions);
}
res.send(details);
});
There result at the console is:
BillId: 549bf0597886c3763e000001
BillId: 54a014bfac01ca3526000001
BillId: 54a015753547a6c026000001
^ Good result - Comes from the first console.log inside the for().
and then:
Transaction BillId: 54a015753547a6c026000001
Transaction BillId: 54a015753547a6c026000001
Transaction BillId: 54a015753547a6c026000001
^ Bad result - Its repeating the last result from the for() and I need all results.
At the first console.log(), when I get the results from the first find() (Bills.find()), I can see all the ids on the log but when I try to get them inside the second Find (Transactions.find()), they repeat the last id.
So in this current state I am not able to query the db for each id. Any help on this is appreciated.
Please let me know if you need any clarification.
Thanks in advance!
Classic mistake of closure function inside the loop.
Your variable value change every loop step. Your function on the other hand will be executed later ( after some time ) when the variable already changed many times.
Take a look on the links for clarification:
Creating closures in loops: A common mistake
JavaScript closure inside loops – simple practical example
Javascript infamous Loop issue?
If your Transactions.find function is asynchronous, its callback runs at a later point in time, long after the for ... in cycle is finished. At that time, billId has the value it was assigned on the last run of the for ... in cycle. Therefore, all runs of the callback print out that value.
If you want to hold on to the value, you'll have to create a closure somewhere, like this:
(function (id) {
Transactions.find(..., function (...) {
// ... use `id` here
});
})(billId);
If bills is an Array or if you're using some utility library like Lo-Dash, you could use iteration function like Array.prototype.map or lodash.each that uses a closure, so that you wouldn't have to create one separately.
Perfect!
Thanks for the tips guys. I resolved it using Underscore each function.
Here is how:
Instead of looping with for, I simple replaced the for by _.each as in the below example:
Bills.find({type: bill_type, endDate: {"$gte" : new Date(year + "-" + month + "-1")}}).find(function(err, bills){
if(err)
res.send(err);
details.bills = bills;
_.each(bills, function(item){
var billId = item._id;
console.log("BillId: " + billId);
Transactions.find({billId : billId}, function(err, transactions){
if (err) {
console.log('error: '+ err)
} else {
console.log("Transaction BillId: " + billId);
}
});
//console.log(transactions);
});
res.send(details);
});
I am attempting to write a test-script, as part of a larger project which will allow me to schedule future text message alerts. In my script, I try to use both node packages cron and node-schedule. I configure a date object set to three minutes in the future. I use both packages to schedule the job. However, the messages are not sent at the right time, but are sent immediately.
I want my code to execute at a set time in the future, does anyone know why the following code would not work?
var CronJob = require('cron').CronJob;
var twilSms = require('./TwilSms');
var schedule = require('node-schedule');
var sendInThree = function(to,message) {
var threeMinutes = 180000;
var inThreeMinutes = new Date(Date.now()+threeMinutes);
process.stdout.write("A text message should be sent at: \n" + inThreeMinutes.toString() + '\n');
//TRIGGERS INSTANTLY
var sched = schedule.scheduleJob(inThreeMinutes,twilSms.sendSms(to,
('Did this message arrive at: ' + inThreeMinutes.toString() + '?')));
//TRIGGERS INSTANTLY
var cronSMS = new CronJob(inThreeMinutes,
twilSms.sendSms(to,
('Did this message arrive at: ' + inThreeMinutes.toString() + '?')),
null, true);
}
sendInThree('13125555555');
NOTES:
function twilSms.sendSms() workes as expected in the Node REPL.
I used a fake number only for uploading to SO.
Thanks for any help.
You should pass in a callable, e.g. an anonymous function as the second parameter to CronJob constructor. In your code, you are passing in the return value of twilSms.sendSms() in which case sendSms() executes immediately (because it's called by the () at the end). It doesn't matter that a function call is a parameter to another function, it executes immediately when the interpreter processes that line.
var cronSMS = new CronJob(inThreeMinutes, function() {
twilSms.sendSms(to,
('Did this message arrive at: ' + inThreeMinutes.toString() + '?')) },
null, true);
Update 1
If you don't want to use an anonymous function when specifying the parameters, you can assign a function to a named variable - but it still has to be a callable as CronJob expects it to be. E.g.
var sendSmsFunc = function() {
twilSms.sendSms(to,
('Did this message arrive at: ' + inThreeMinutes.toString() + '?'))
}
var cronSMS = new CronJob(inThreeMinutes, sendSmsFunc, null, true);
The example you have in your 2nd comment doesn't seem right, because although sendSMS is a callable in that case, it expects two parameters at runtime. CronJob will not pass any parameters so it must be a callable that doesn't expect parameters just simply wants to be called (or executed) at runtime to do its job.
Update 2
If you want to be able to pass in parameters to sendSms() at the same time when specifying parameters to CronJob(), you can use a function that returns a function, e.g.
var sendSmsFunc = function(to, message) {
return function() {
twilSms.sendSms(to, message);
}
}
var cronSMS = new CronJob(inThreeMinutes, sendSmsFunc("123", "test message"), null, true);