Recursive promise not returning expected value - javascript

With a slight modification, I am attempting to use the code provided by Bergi in jQuery Recursive AJAX Call Promise. In my case I make an AJAX call to test if a username is already used. If it is already in use then compose a new username and test that one. Once we have a username that is not in use then we are done and return that unused username. However, I am not getting the expected return value. The return value I get is undefined. The console log statement:
console.log("Return => " + username);
just before the return from the requestUsername function shows that I am returning a good value, but it is not making it to the:
requestUnused().done(function(unused_uname)
statement. Here is my code:
$(document).ready(function() {
function request(query_val) {
// return the AJAX promise
return $.ajax({
url: "/php/is_dup_ad_json.php",
method: 'GET',
dataType: 'json',
data: {
query: query_val, sid: Math.random()
},
});
}
function requestUsername(username) {
console.log("Initial => " + username);
return request(username).then(function(ajax_json){
$.each(ajax_json, function(key, value) {
$.each(value, function(k, v) {
if ((k == "duplicate") && (v > 0)) {
// try again with a different username
var first_initial = fname.substr(0,1);
var surname = lname.substr(0,6);
var idx = v + 1;
var tmpUname = surname + first_initial + idx;
console.log("Temp => " + tmpUname);
return requestUsername(tmpUname);
}
else {
console.log("Return => " + username);
return username;
}
});
});
});
}
function requestUnused(){
var fname = "bugs";
var lname = "bunny";
var first_initial = fname.substr(0,1);
var surname = lname.substr(0,7);
var init_uname = surname + first_initial;
return requestUsername(init_uname);
}
$("#test").on('click', function() {
requestUnused().done(function(unused_uname) {
console.log("Done => " + unused_uname);
});
});
});

Without debugging tools at hand, I would guess that the return value from "requestUnused()," which is a ".then" returned from "requestUsername" is competing with the ".done". I believe ".done" and ".then" serve a similar purpose. If you want to keep a modular approach, separating the functions as it were, you could define the function in the ".then" externally and remove "requestUsername" entirely. Then (no pun intended) call "request" directly in "requestUnused," applying the ".then" functionality extracted previously in the ".click" function instead of ".done."
Alternatively, you could simply call "requestUnused()" in the click function without a ".done".

Related

Using async / await with $.ajax

This is my first attempt to utilize asynchronous javascript. I've tried hooking up every incarnation of promises that I can find, but have not been able to write them successfully to get my string to return (i.e. $.Deferred, async/await, Promise, callbacks, relying on .done). async:false as an ajax variable works, but I'm trying to avoid settling for what I understand is bad practice. I would love to use async/await because it is so concise, but at this point I'm up for anything that works. I have a suspicion that I'm trying to utilize the $.ajax return in an incorrect manner.
Much appreciation for a working return of the string wholename (a randomized first and last name), examples of a few versions for my own education even more appreciated!
function Actor(gender, name) {
if (gender == "" || gender == undefined) {this.gender = "female";} else this.gender = gender;
if (name == "" || name == undefined) {this.name = makeName(this.gender);} else this.name = name;
}
function getPromiseName(sex) {
return promise = $.ajax({
type: "GET",
url: "TMxml.xml",
dataType: "xml"//,
//async: false //this works for returns, but is apparently bad practice
});
}
function makeName(sex) {
var fnames = [];
var lnames = [];
var thexml = getPromiseName(sex);
thexml.done(function(xml) {
if (sex == "male") {
$(xml).find('malename').children().each(function(){
fnames.push($(this).text());
});
}
if (sex == "female") {
$(xml).find('femalename').children().each(function(){
fnames.push($(this).text());
});
}
$(xml).find('lastname').children().each(function(){
lnames.push($(this).text());
});
wholename = fnames[Math.floor(Math.random() * fnames.length)] + " " + lnames[Math.floor(Math.random() * lnames.length)];
alert("wholename = " + wholename); //successfully alerts a randomized name
return wholename; //but returns undefined, or [object Promise] when using async/await
});
}
Here's what I would suggest. This is test data, so the names don't make sense, but of course all you'd have to do is change the url, the getRandomName function and the doStuffWithActor function based on your code above. (As you can see, I would recommend keeping the fetching logic and the actor initialization logic as separate as possible :)
class Actor {
constructor(name, gender) {
this.name = name;
this.gender = gender;
}
}
Array.prototype.sample = function () {
if (!this.length) return null;
const randIdx = Math.floor(Math.random() * this.length);
return this[randIdx];
};
const createActor = async (url, name, gender, callback) => {
gender = gender || 'female';
if (!name) {
const response = await fetch(url);
const data = await response.text();
name = getRandomName(data, gender);
}
const actor = new Actor(name, gender);
if (callback) callback(actor);
};
const getRandomName = (xmlData, gender) => {
const names = xmlData.split(/\s+/);
const femaleNames = names.slice(0, names.length / 2);
const maleNames = names.slice(names.length / 2);
return gender === 'female' ? femaleNames.sample() : maleNames.sample();
};
const doStuffWithActor = (actor) => {
console.log('Actor name:', actor.name);
console.log('Actor gender:', actor.gender);
console.log('\n');
};
createActor('https://httpbin.org/xml', '', '', doStuffWithActor);
createActor('https://httpbin.org/xml', '', 'male', doStuffWithActor);
You're doing it wrong. You must understand that when you work with async mode, you must use callback function to trigger the function that you want.
if you want manualy find out was sended ajax successfull, you must loop it's status with timer and check the success status - that is not recomended.
The reason that your code is working in sync mode is that, the whole javascript freezes until message is responded - that is not recomended also =)
Working ajax function:
function SendAjax($url_mode, $data_serialize, $print_container, $callback_function) {
$options = {
type: "GET",
url: $url_mode,
data: $data_serialize,
dataType: "xml",
success: function (msg) {
if ($print_container !== '') {
$print_container.html(msg);
}
if (typeof $callback_function !== "undefined") {
$callback_function(msg);
}
},
error: function (xhr, str) {
alert('Error: ' + xhr.responseCode);
}
};
$.ajax($options);
}
Calling SendAjax function:
$(document).delegate(".btn-grid", "click", function () {
SendAjax("TMxml.xml", "?any_key=true", $(".print-container-if-needed-or-set-null"), $Callback_function_like_your_makeName_function);
});

Ajax call inside forEach loop Angular

I am having an Array that contains objects in AngularJS. Based on the value of a property (snooz) of these objects I have to call a POST request (getData.sonnzeUpdate()). After going through each object in the array, finally, I have to call a GET request. My issue is the GET request (inside the function $scope.getTableData) is executed before getting the response (res in .then(function(res){}) of the POST request.
I have tried with angular.forEach() and $q.
Here is my code sample
var notifiedAlarms = [];
var d = new Date();
var checkTine = d.getHours() + "-" + d.getMinutes() + "-" + "00";
angular.forEach(snoozedData, function (snoozed_asset, asset_key) {
if (snoozed_asset.snooz == checkTine) {
var data = {};
snoozed_asset.snooz = '';
data.data = snoozed_asset;
var deferred = $q.defer();
getData.sonnzeUpdate(data).then(function (res) {
if (res.status == '200') {
toastr.info('Alarm with property ' + data.data.actualFailureArea + ' is activated');
// $scope.getTableData(); //donot want to call it here. as same call will for multiple time
notifiedAlarms.push(deferred.promise);
} else {
// console.log('Error in update');
}
});
} else {
// no matching snooz
}
});
$q.all(notifiedAlarms).then($scope.getTableData());
In your code, notifiedAlarms is empty at that point: $q.all(notifiedAlarms), because you add the promises to that Array, after they have finished.
And avoid the Deferred antipattern. getData.sonnzeUpdate() already returns you a Promise.
angular.forEach(snoozedData, function (snoozed_asset, asset_key) {
if (snoozed_asset.snooz != checkTine) return;
snoozed_asset.snooz = '';
notifiedAlarms.push(
getData.sonnzeUpdate({ data: snoozed_asset }).then(function(res) {
if (res.status == '200') {
toastr.info('Alarm with property ' + data.data.actualFailureArea + ' is activated');
} else {
throw new Error('Error in update');
}
})
);
});
With $q.all(notifiedAlarms).then($scope.getTableData()) you're executing $scope.getTableData() directly instead of telling it to call it once promises are resolved. Hence, it gets executed earlier than you want.
Change it to $q.all(notifiedAlarms).then($scope.getTableData); to get desired behavior.
Here's an example elaborating this:
jsfiddle example
Notice how in the fiddle, I have both ways,
myownservice.UpdateSomeData().then($scope.setValue)
myownservice.UpdateSomeData().then($scope.setValue2())
And, in HTML only value gets updated and not value2

NodeJS Promises: Proper way to combine multiple HTTP requests returns

Have a NodeJS process that reaches out to a webservice for something called Kudos. These kudos are sent from one person to another person/or group of people. What I'm trying to do is create one message that has the following:
Kudos from {poster} to {receiver/s}
{Kudos Message}
Currently I have the process working correctly for poster to one receiver. I am struggling with making it work with getting the multiple names of the receivers.
The problem stems from the fact that the section where it returns the users receiving the kudos, it only provides the user id. So I need to make another call to obtain the user's name. I can easily get the promises to work for the one user, but I can seem to get the multiple user properly.
The JSON data that contains the multiple users looks something like this:
"notes_user": [
{
"id": "1060",
"note_id": "795",
"user_id": "411"
},
{
"id": "1061",
"note_id": "795",
"user_id": "250"
},
{
"id": "1062",
"note_id": "795",
"user_id": "321"
}
],
Here is the function that does the majority of the work:
getMaxId returns a database index of that highest kudos currently processed, and getKudos just returns the json dataset of "kudos".
function promisifiedKudos() {
var maxid;
var newmaxid;
Promise.all([getMaxId(), getKudos()])
.then(function(results) {
maxid = results[0];
var kudos = results[1];
newmaxid = kudos[0].id;
return kudos.filter(function(kudo) {
if (maxid < kudo.id) {
return kudo;
}
})
})
.each(function(kudo) {
return getTribeUserName(kudo);
})
.then(function(results) {
return results.map(function(kudo) {
var message = "Kudos from " + kudo.poster.full_name + " to " + kudo.kudo_receiver_full_name + "\r\n";
message += "\r\n";
return message += entities.decode(striptags(kudo.note));
})
})
.each(function(message) {
return postStatus(message);
})
.then(function() {
var tribehr = db.get('tribehr');
console.log(new Date().toString() + ":Max ID:" + newmaxid);
tribehr.update({ endpoint: "kudos" }, { $set: { id: newmaxid } });
})
.done(function(errors) {
console.log("Run Complete!");
return "Done";
});
}
The helper function getTribeUserName()
function getTribeUserName(kudo) {
return new Promise(function(fulfill, reject) {
var id = kudo.notes_user[0].user_id;
var options = {
url: "https://APIURL.com/users/" + id + ".json",
method: "GET",
headers: {
"Authorization": "Basic " + new Buffer("AUTHCODE" + AUTHKEY).toString('base64')
}
}
request.getAsync(options).then(function(response) {
if (response) {
var data = JSON.parse(response.body)
kudo.kudo_receiver_full_name = data.User.full_name;
fulfill(kudo);
} else {
reject("Get Tribe User Name Failed");
}
});
});
}
I've tried adding a helper function that calls the getTribeUserName() that looks like this:
function getTribeUsers(kudo) {
return new Promise(function(fulfill, reject) {
kudo.notes_user.map(function(user) {
//Make calls to a getTribeUserName
})
});
}
But the outcome is that the user names are undefined when the finalized message is put together.
Any pointers in how to use promises better would be extremely helpful. This is really my first stab with them and I hope I'm heading in the right direction. I know I need to add the error checking in, but currently I'm just trying to get the process working for multiple users.
if you need to use the result of a promise passed as parameter of the resolve function then you can catch it in the then onFulfilled callback.
If you need to pass some data obtained within a then method of a chain to another then you just have to return it and catch it through the onFulfilled callback of the following then.
object.somePromise().then(function(param){
var data = someFunction();
return data;
}).then(function(param){
//param holds the value of data returned by the previous then
console.log(param);
});
If it's a matter of getting multiple TribeUserNames asynchronously, then you need somehow to aggregate promises returned by multiple calls to getTribeUserNames().
You could write Promise.all(array.map(mapper)) but Bluebird provides the more convenient Promise.map(array, mapper).
Bluebird's .spread() is also convenient, for referencing maxid and kudos.
Here it is in as simple a form as I can manage :
function promisifiedKudos() {
return Promise.all([getMaxId(), getKudos()])
.spread(function(maxid, kudos) {
var newmaxid = kudos[0].id;
// The following line filters (synchronously), adds TribeUserNames (asynchronously), and delivers an array of processed kudos to the next .then().
return Promise.map(kudos.filter((kudo) => kudo.id > maxid), getTribeUserName)
.then(function(filteredKudosWithTribeUserNames) { // in practice, shorten arg name to eg `kudos_`
return Promise.map(filteredKudosWithTribeUserNames, function(kudo) {
return postStatus("Kudos from " + kudo.poster.full_name + " to " + kudo.kudo_receiver_full_name + "\r\n\r\n" + entities.decode(striptags(kudo.note)));
});
})
.then(function() {
var tribehr = db.get('tribehr');
console.log(new Date().toString() + ":Max ID:" + newmaxid);
return tribehr.update({ endpoint: 'kudos' }, { $set: { 'id': newmaxid } });
});
})
.then(function() {
console.log('Run Complete!');
}).catch(function(error) {
console.log(error);
throw error;
});
}
getTribeUserName() needs to return a promise, and can be written as follows :
function getTribeUserName(kudo) {
var options = {
'url': 'https://APIURL.com/users/' + kudo.notes_user[0].user_id + '.json',
'method': 'GET',
'headers': {
'Authorization': 'Basic ' + new Buffer('AUTHCODE' + AUTHKEY).toString('base64')
}
}
return request.getAsync(options).then(function(response) {
// ^^^^^^
if(response) {
kudo.kudo_receiver_full_name = JSON.parse(response.body).User.full_name;
} else {
throw new Error(); // to be caught immediately below.
}
return kudo;
}).catch(function(error) { // error resistance
kudo.kudo_receiver_full_name = 'unknown';
return kudo;
});
}
Further notes:
By nesting Promise.map(...).then(...).then(...) in the .spread() callback, newmaxid remains available through closure, avoiding the need for an ugly outer var.
Promise.map() is used a second time on the assumption that postStatus() is asynchronous. If that's not so, the code will still work, though it could be written slightly differently.

How to chain two ajax requests with promises

I am having trouble with ajax/promises. I have two ajax requests total, with the second ajax call relying data to be returned by the first ajax call.
My first ajax call finds Latitude, Longitude, and country code of the value of #search.
My second ajax call finds the weather of that city, but the API URL is dependent on the Latitude, Longitude and country code that my first ajax call returns. So the second ajax call can't be started until the first one is finished.
My logic here is that var ajax1 is assigned a promise, and var ajax2 starts after ajax1.then() checks that ajax1's promise is resolved. Then ajax2 runs and returns another promise. Finally ajax2.done starts after it checks that ajax2's promise is resolved, and then starting my successWeatherFunction().
My problem is that ajax2.done is not working, as the console.log("test") is not showing up on the console. The two earlier console.logs, console.log(info) and console.log(weatherApiUrl) are working.
Thanks!
$("#search").keypress(function(event) {
if (event.which === 13) {
var searchCity = $("#search").val();
var jsonURL = "http://autocomplete.wunderground.com/aq?query=" + searchCity + "&cb=?"
var ajax1 = $.getJSON(jsonURL);
var ajax2 = ajax1.then(function(data) {
var info = [];
info.push(data["RESULTS"][0]["name"]);
info.push(data["RESULTS"][0]["c"]);
info.push(data["RESULTS"][0]["lat"]);
info.push(data["RESULTS"][0]["lon"]);
console.log(info);
var searchLat = info[2];
var searchLng = info[3];
var countryCode = info[1];
if (countryCode === "US") {
var weatherApiUrl = "https://api.forecast.io/forecast/{APIKEY}/" + searchLat + "," + searchLng + "?exclude=minutely" + "&callback=?";
} else {
var weatherApiUrl = "https://api.forecast.io/forecast/{APIKEY}/" + searchLat + "," + searchLng + "?exclude=minutely" + "?units=si" + "&callback=?";
console.log(weatherApiUrl);
}
return $.getJSON(weatherApiUrl);
});
ajax2.done(function(data){
console.log("test");
successCityWeather(data);
});
Your code use then and done. done is the old promises jQuery syntax so you should use only then.
The following code works for me :
$(function() {
$.get('/test').then(function() {
console.log('First request end');
return $.get('/test');
}).then(function() {
console.log('second request end');
});
});
But in your case, maybe a one of your request fail. Give a second parameter to then to log the error :
$.getJSON('...').then(function(data) {
console.log('success', data);
}, function(data) {
console.log('fail', data);
});
If not sure, always use always() handler. That way you will know if the request actually finished with error or not at all.
$.ajax( ...params... )
.always(function(jqXHR, textStatus) {
if (textStatus != "success") {
alert("Error: " + jqXHR.statusText); //error is always called .statusText
} else {
alert("Success: " + jqXHR.response); //might not always be named .response
}});
$.post(jsonURL)
.then(function (data) {
var info = [];
// some actions
return $.getJSON(weatherApiUrl);
})
.then(function(data, status, promise) {
// some actions
successCityWeather(data);
})

How to return an array from a Javascript Facebook API request?

Hopefully there is an easy way to do this and my Javascript skills are just lacking. I am wanting to call a function that will get some Facebook posts, add them to an array and return to use elsewhere. Current code is below.
function GetFaceBookStream(name, max) {
FB.init({ apiKey: 'removed for post' });
var lastDate = '2011-04-29Z00:00:00';
var faceBookArray = [];
var faceBookString;
FB.api("/" + name + "/feed", { limit: max, since: lastDate }, function (response) {
var sb = string_buffer();
for (var i = 0; i < response.data.length; i++) {
var post = response.data[i];
sb.append("<li class='facebook'>");
sb.append("<img alt=\"Facebook\" src='Images\\Carousel\\fbIcon.png\' />");
sb.append("<h4>FACEBOOK</h4>\n");
sb.append("<div class=\"from-name\">" + post.from.name + "</div>");
sb.append("<div class=\"time\">" + post.created_time + "</div>");
if (post.message != undefined) {
sb.append("<div class=\"message\">" + post.message + "</div>");
}
sb.append("</li>stringSplitMarker");
}
faceBookString = sb.toString();
faceBookArray = faceBookString.split('stringSplitMarker');
});
return faceBookArray;
}
I realize this set up won't work due to variable scope in Javascript, but this is basically what I'm trying to achieve. Any help will be greatly appreciated!
You're making an asynchronous AJAX request.
The callback only runs after your code finishes.
You need to pass the data back using a callback.
For example:
function GetFaceBookStream(name, max, callback) {
...
FB.api(..., function(response) {
...
callback(something, else);
});
}
You can call the function by supplying a callback to receive the response:
GetFaceBookStream(name, max, function(param1, param2) {
//This code runs later and can use the response.
});

Categories