Only Push success callback data to array, exclude error 404 - javascript

I have issues with handling error 404. What I want to do is, the data I get from making a request to the PokeApi, I want to push it into an array. If all request are successful to the API and pushed into the array, everything works fine, then I can get out my data I want, move it to my then() and append it to a div. However, once a request are getting a 404 error not found, nothing gets appended to my div. How do I handle the 404 and continue to loop and be able to append my data?
I have tried, using if statements and fail(), done() but just doesn't work as I want it to.
function get_sprites(poke_name){
var arr = [];
for(var i = 0; i<poke_name.length; i++){
arr.push($.getJSON("https://pokeapi.co/api/v2/pokemon/" +
poke_name[i]));
}
$.when.apply($, arr).then(function(){
var storeObjPoke = [];
for(var i = 0; i < arguments.length; i++){
var name = arguments[i][0].name
var upperCase_name = name.charAt(0).toUpperCase() +
name.slice(1);
var objPoke = {
id : arguments[i][0].id,
name : upperCase_name,
imgUrl : arguments[i][0].sprites.front_default
}
$("<div class='pokemon' id='"+ arguments[i][0].id +"'>" +
upperCase_name + "<br><img src='" +arguments[i]
[0].sprites.front_default+"'alt=''/><br>" + arguments[i][0].id +
"</div>" ).appendTo("#display_pokemon");
}
}
}
I expect it to be able to display all my objects in my div, but when I get a 404 error, nothing gets appended to div.

So it looks like .when is similar to promise.all with the fact that it's an all or nothing kinda thing. All promises have to be resolved (successful) for the .then function to be fired.
So what is happening for you is once the promise with the 404 is rejected, jQuery calls the .fail() function immediately (which you don't have so its swallowing the error) and the rest of the promises may be unfulfilled and .then() is not called.
From jQuery.when()
In the case where multiple Deferred objects are passed to jQuery.when(), the method returns the Promise from a new "master" Deferred object that tracks the aggregate state of all the Deferreds it has been passed. The method will resolve its master Deferred as soon as all the Deferreds resolve, or reject the master Deferred as soon as one of the Deferreds is rejected.
and
In the multiple-Deferreds case where one of the Deferreds is rejected, jQuery.when() immediately fires the failCallbacks for its master Deferred. Note that some of the Deferreds may still be unresolved at that point.
so what you are trying to do doesn't seem possible without some extra fluff. You can check out this SO answer to get a more general understanding of some of the fluff you might need Wait until all ES6 promises complete, even rejected promises including a future proposal promise. allSettled() which would solve your problem, but its only compatible with a few browsers currently.
2 other thoughts
Is it important that you wait for all requests to come back before you start displaying them in the UI? If not then just call the requests in a loop.
For example,
get_sprites(["squirtle", "charmander", "not_a_pokemon", "bulbasaur"])
function get_sprites(poke_name) {
for (let i = 0; i < poke_name.length; i++) {
$.getJSON("https://pokeapi.co/api/v2/pokemon/" + poke_name[i], addPokemon)
.fail(function(err) {
console.log(`error getting pokemon: ${poke_name[i]} - ${err.status}`);
});
}
function addPokemon(pokemon) {
var storeObjPoke = [];
var name = pokemon.name
var upperCase_name = name.charAt(0).toUpperCase() + name.slice(1);
var objPoke = {
id: pokemon.id,
name: upperCase_name,
imgUrl: pokemon.sprites.front_default
}
$("<div class='pokemon' id='" + pokemon.id + "'>" +
upperCase_name + "<br><img src='" + pokemon.sprites.front_default + "'alt=''/><br>" + pokemon.id +
"</div>").appendTo("#display_pokemon");
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="display_pokemon"></div>
If it is important to not show anything until all requests are finished, just keep a counter on the requests finished using .always() on the request and check after each request if the counter equals poke_name.length. If so, then you know a request for each pokemon has finished (success or fail) and then display all the results.
example of 2
get_sprites(["squirtle", "charmander", "not_a_pokemon", "bulbasaur", "onix", "mewtwo", "pikachu"])
function get_sprites(poke_names) {
let poke_names_length = poke_names.length;
let finished_requests = 0;
let poke_array = [];
for (let poke_name of poke_names) {
$.getJSON("https://pokeapi.co/api/v2/pokemon/" + poke_name)
.always(function() {
finished_requests++
})
.done(addPokemon)
.fail(function(err) {
console.log(`error getting pokemon: ${poke_name} - ${err.status}`);
})
}
function addPokemon(pokemon) {
let upperCase_name = pokemon.name.charAt(0).toUpperCase() + pokemon.name.slice(1);
let objPoke = {
id: pokemon.id,
name: upperCase_name,
imgUrl: pokemon.sprites.front_default
}
poke_array.push(objPoke);
if (poke_names_length === finished_requests) {
displayPokemon();
}
}
function displayPokemon() {
let ordered_pokes = poke_array.sort(function(a, b) {
return a.id - b.id;
});
for (let poke of ordered_pokes) {
$("<div class='pokemon' id='" + poke.id + "'>" +
poke.name + "<br><img src='" + poke.imgUrl + "'alt=''/><br>" + poke.id + "</div>").appendTo("#display_pokemon");
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="display_pokemon"></div>

Related

Promise resolve misses one value for asynchronous request loop

Thanks to the help from here I could build a loop for posts and resolve the promises in order to handle asynchronous requests. But for some reason the loop to get the resolved promises and according values always misses one step. I tried to apply JotaBe's answer and it worked out, except there is one value missing. Everything else is fine.
The simplified function I call is:
var logs = function (outString, saveCSV) {
var promises = [];
var count = 0;
for (i = 1; i <= maxDevice; i++) {
promises.push($.post(Type1));
promises.push($.post(Type2));
promises.push($.post(Type3));
}
promises.push($.post(Type4));
var promiseResolve = $.when.apply($, promises);
promiseResolve.then(function () {
console.log(promises[1].promise().state());
console.log(promises[2].promise().state());
for (i = 0; i < promises.length; i++) {
promises[i].then(function (data, textStatus) {
var src = this.url;
var arg = arguments;
console.log(i + ": " + textStatus);
if (posttype2 or 3){String1 += data
} else if (posttype4) > 0)
{
String2 += data
} else
{
string3 += data
}
});
}
outString += String3+ "\n" + String2+"\n" + string1;
saveCSV(outString, filename);
});
};
The console.log(i + ": " + textStatus);shows
0: success
2: success
3: success
4: success
5: success
6: success
7: success
8: success
9: success
So i = 1is never resolved, even though console.log(promises[1].promise().state()); states promises[1] IS resolved.
If I set a breakpoint before the promiseResolve the missing promise is handled, though, while delaying the code with a timeout doesn't seem to help.
I also tried to use .done, instead of .then with the same result. The data missing is the largest data package of the loop. It can't be the syntax, since the other promises fetched with the same get in the loop resolve just fine.
So what am I doing wrong, as I can't understand why this one is missing. As far as I understood $when.applyis used to make sure the promises are resolved (which the console log states are).
How to handle this to get ALL values into the outstring?
Edit:
I tried some more console.log lines to check for values, or if something isn't resolved. So a console.log(i); right before the promises[i].then(function (data, textStatus)´ shows i = 1 is called. The row is complete 0, 1, 2... but theconsole.log(i + ": " + textStatus);after the function misses the 1, so it shows ´promises[1].then(function (data, textStatus) {...} is not called.
Logging
console.log(promises[1].promise().state());
console.log(promises[1].responseText);
right before the for.. loop shows the promise state is "resolved" and the responseText shows the string I want to attach to the outstrings. I also tried the proposed solution of Jaromanda X, but it did not help (thanks for the time), neither did using different combinations of using .doneinstead of .thenin either resolve function.
Putting a breakpoint before the promiseResolve.then seems to help, even if I click "run" as fast as I can. Have to try shortening that time, to be sure.
Adding another set of things I tried:
Splitting the posts/gets and the resolve into two functions and using the resolve as callback brought no success, neither did changing i to some unused variable, nor using for promise in promises to initiate the loop. I start running out of ideas to try, even more as promiseResolve returns resolved right at the promiseResolve.then function, so the request in promise[1] should be finished.
What seems to work, though I do not understand why and doesn't feel like the right way to solve the problem is encapsulating everything inside the promiseResolve.then function into a window.setTimeout(function(){...},1, so it looks more like
window.setTimeout(function(){
for (i=0 ; i < promises.length; i++) {
...
};
outString += spotString + "\n" + ioString + "\n" + logString;
saveCSV(outString, filename);
}, 1);
So, this one ms delay helps, but it doesn't feel like a clean solution. Can anyone explain why, or what I am missing? There must be a better way.
you have asynchronous code within the for loop that is using i
try this code
promiseResolve.then(function () {
console.log(promises[1].promise().state());
console.log(promises[2].promise().state());
for (i = 0; i < promises.length; i++) {
(function(i) { // ***** add this
promises[i].then(function (data, textStatus) {
var src = this.url;
var arg = arguments;
console.log(i + ": " + textStatus);
if (posttype2 or 3) {
String1 += data
} else if (posttype4) > 0) {
String2 += data
} else {
string3 += data
}
});
}(i)); // ***** add this
}
outString += String3 + "\n" + String2 + "\n" + string1;
saveCSV(outString, filename);
});
You'll have to make the changes from the invalid javascript to your actual code as above ... the additional lines are marked
what that code does is create a closure where i wont be changed before it is logged due to the asynchronous nature of the function it is logged in

angular.forEach Resolving Nested Promises

I have to make sequential AJAX calls after retrieving a collection of data. I am having issues resolving the nested promises.
Basically, I need to extend each object returned in my first collection with a property of ActionItems and set it's value with a promise then resolve each promise in the collection.
Any help would be greatly appreciated.
Factory
$http.get(urlBase + 'Project?$expand=Plant,CreatedBy,ModifiedBy,Plant/Contacts').then(function(success){
var contents = {};
contents = success.data.d.results;
return contents;
})
.then(function(contents){
var contentPromises = [];
angular.forEach(contents, function(content) {
contentPromises.push(
$http.get(urlBase + "ActionItems?$filter=ProjectId eq " + content.Id ).then(function(success){
content['ActionItems'] = success.data.d.results;
})
);
});
return $q.all(contentPromises).then(function() {
return contents;
});
});
Current Output is undefined
Well turns out this method works, but the key to getting your data back is returning it...
//Forgot the return below...
return $http.get(urlBase + 'Project?$expand=Plant,CreatedBy,ModifiedBy,Plant/Contacts').then(function(success){
var contents = {};
contents = success.data.d.results;
return contents;
})
.then(function(contents){
var contentPromises = [];
angular.forEach(contents, function(content) {
contentPromises.push(
$http.get(urlBase + "ActionItems?$filter=ProjectId eq " + content.Id ).then(function(success){
content['ActionItems'] = success.data.d.results;
})
);
});
return $q.all(contentPromises).then(function() {
return contents;
});
});
Thanks for all who helped.
Your issue lies within the $http.get(...).then() part.
The documentation for .then is telling us that "This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback".
So the promise returned by .then is different than the one returned by $http.get. And you are responsible of resolving or rejecting it (by returning)! The promise returned by .then is the one pushed to contentPromises.
Thus you need something like this:
angular.forEach(contents, function(content) {
contentPromises.push(
$http.get(urlBase + "ActionItems?$filter=ProjectId eq " + content.Id ).then(function(success){
content['ActionItems'] = success.data.d.results;
return success;
})
);
});
You'd do well to implement the errorCallback too.

Asynchronous loading data issue AngularJS and Firebase

I am having difficulties loading data from Firebase into my AngularJS factory and thus controllers.
Basically I have the following factory:
.factory('usersList', ['fbutil', function(fbutil) {
return {
all: function() {
return fbutil.syncArray('users');
},
get: function(userId) {
fbutil.syncArray('users').then( function(result) {
window.alert(result.length) // FIRST ALERT
var x = 0;
for (var i = 0, len = result.length; i < len; i++) {
if (result[i].uid == userId) {
window.alert("fount id") // SECOND ALERT
x = i;
} else {
window.alert("nope"); // THIRD ALERT
}
}
return result[x];
}) // then
} // get
} // return
}]); // usersList
And my controller looks like:
.controller('OverviewCtrl', ['$scope', 'fbutil', 'usersList', function($scope, fbutil, usersList) {
$scope.usersList = usersList.all();
$scope.testUser = usersList.get("simplelogin:29");
// OTHER CODE
};
}])
In my HTML file, when I call {{usersList}} then it produces the result:
[{"color":"#CC0066","email":"a#a.com","name":"Eva","uid":"simplelogin:27"},{"color":"#009933","email":"b#b.com","name":"Semko","uid":"simplelogin:28"},{"color":"#CC0066","email":"c#c.com","name":"Caroline","uid":"simplelogin:29"}]
But testUser does not load, just shows {{tesUser}} in the index file.
Does anyone know how to handle this correctly? Without using the then(), which in this example also does not work, I figured out from the first alert that the result.length equaled 0, which gave me the suggestion that I am dealing with asynchronous loading. That is why I am trying to handle it whit .then() but apparently it is not working.
To handle a promise in angularjs the best way it to use defer values, since it allows you to process it before returning the data while keeping everything non blocking. With $http I would process like this :
function get(id) {
var deferred = $q.defer();
var url = "anUrlwithid";
$http.get(url).success(function(data, status) {
logger.logDebug('Successful GET call to ' + url + '\n, results ' + status + ': ' + data);
deferred.resolve(function(data){
//do something to your data then return
});
}).error(function(data, status) {
logger.logDebug('Failed GET call to ' + url + '\n, results ' + status + ': ' + data);
deferred.reject(data);
});
return deferred.promise;
}
And to process it in the controller :
get(1).then(function(data){//update scope or do something with the data processed});
You should be able to use that with your fbutil since it returns a promise I think.
Hope it helps
More details on the Q module here : https://docs.angularjs.org/api/ng/service/$q
PS: the logger is one of my personal service, just use console.log instead

Check if a promise finished in Javascript?

I am using PDF.js to extract text content from a PDF which I will use next for some more processing, For this,
var complete=0;
var full_text="";
var PDF_render = PDFJS.getDocument("x.pdf").then(function(pdf) {
var page_text = {};
for (i = 1; i <= pdf.numPages; i++){
pdf.getPage(i).then( function(page){
var n = page.pageNumber;
page.getTextContent().then( function(textContent){
var page_text_part = "";
textContent.items.forEach(function (textItem){
page_text_part += textItem.str;
page_text_part += " ";
});
page_text[n] = page_text_part + "\n\n";
++complete;
if (complete == pdf.numPages){
for( var j = 1; j <= pdf.numPages; j++)
full_text += page_text[j];
}
});
});
}
});
The issue is that PDF.js returns promises and they are executed asynchronously, however I need to perform some post processing on the returned text. For this I need to wait for the promises to fully execute and only then move on. How does one achieve this? Please help.
Unfortunately this question asks one thing in title (how to check if Promise finished), and another in the body (how to continue once Promise is finished). Google points to this when searching for how to check if Promise is finished, so I'm answering the question in the title.
To check if a promise if is finished, you can use Promise.race - it will not wait for pending promise if given non-promise value, e.g.
const statusOrValue = await Promise.race([myPromise, 'pending']);
if (statusOrValue === 'pending') {
// Not yet finished
}
Note that this method gives the result asynchronously - await is necessary to check if promise is finished. I don't know if there is a reliable way to check whether promise is completed synchronously.
We can use a much more direct approach, no need for counters etc.
Two things - promises chain, and waiting for multiple promises is done with Promise.all:
var pdf = PDFJS.getDocument("x.pdf");
pdf.then(function(pdf) { // wait for the PDF to load
var pages = [];
for (i = 1; i <= pdf.numPages; i++){ // for each pages
pages.push(pdf.getPage(i).then(function(page){ // push the promise
return page.getTextContent();
}).then(function(textContent){
var page_text_part = "";
textContent.items.forEach(function (textItem){
page_text_part += textItem.str + " ";
});
return page_text_part + "\n\n"; // you can use return values
}));
}
return Promise.all(pages); // wait for all of them to be done
}).then(function(results){
// you can access all the results here
// containing all pages
});
You can use Promise.all to wait for multiple promises to have resolved.
In your case, the code should look like
PDFJS.getDocument("x.pdf").then(function(pdf) {
var pages = [];
for (var i = 1; i <= pdf.numPages; i++) {
pages.push(pdf.getPage(i).then(function(page) {
return page.getTextContent();
}).then(function(textContent) {
return textContent.items.map(function(textItem){
return textItem.str;
}).join(" ") + " \n\n";
});
return Promise.all(promises);
}).then(function(page_texts) {
var full_text = page_texts.join("");
// now do something with the result
});

How to know when a long series of async calls is finished in AngularJS?

I have a service SQLService on my PhoneGap/AngularJS app that runs when the app is loading. It iterates through a long array of Guidelines, and makes a DB transaction for each one. How can I signal that the final transaction has been completed?
What I want to have happen is something like:
In the controller, call `SQLService.ParseJSON`
ParseJSON calls `generateIntersectionSnippets`
`generateIntersectionSnippets` makes multiple calls to `getKeywordInCategory``
When the final call to getKeywordInCategory is called, resolve the whole chain
SQLService.ParseJSON is complete, fire `.then`
I really don't understand how to combine the multiple asynchronous calls here. ParseJSON returns a promise which will be resolved when generateIntersectionSnippets() is completed, but generateIntersectionSnippets() makes multiple calls to getKeywordInCategory which also returns promises.
Here's a simplified version of what's not working (apologies for any misplaced brackets, this is very stripped down).
What I want to happen is for $scope.ready = 2 to run at the completion of all of the transactions. Right now, it runs as soon as the program has looped through generateIntersectionSnippets once.
in the controller:
SQLService.parseJSON().then(function(d) {
console.log("finished parsing JSON")
$scope.ready = 2;
});
Service:
.factory('SQLService', ['$q',
function ($q) {
function parseJSON() {
var deferred = $q.defer();
function generateIntersectionSnippets(guideline, index) {
var snippet_self, snippet_other;
for (var i = 0; i < guideline.intersections.length; i++) {
snippet_self = getKeywordInCategory(guideline.line_id, snippets.keyword).then(function() {
//Should something go here?
});
snippet_other = getKeywordInCategory(guideline.intersections[i].line_id, snippets.keyword).then(function() {
//Should something go here?
});
}
}
deferred.resolve(); //Is fired before the transactions above are complete
}
generateIntersectionSnippets();
return deferred.promise;
} //End ParseJSON
function getKeywordInCategory(keyword, category) {
var deferred = $q.defer();
var query = "SELECT category, id, chapter, header, snippet(guidelines, '<b>', '</b>', '...', '-1', '-24' ) AS snip FROM guidelines WHERE content MATCH '" + keyword + "' AND id='" + category + "';",
results = [];
db.transaction(function(transaction) {
transaction.executeSql(query, [],
function(transaction, result) {
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
var row = result.rows.item(i);
results.push(row);
}
}
},defaultErrorHandler);
deferred.resolve(responses);
},defaultErrorHandler,defaultNullHandler);
return deferred.promise;
}
return {
parseJSON : parseJSON
};
}]);
I'd appreciate any guidance on what the correct model is to doing a chain of promises that includes an iteration across multiple async transactions- I know that how I have it right now is not correct at all.
You can use $q.all() to wait for a list of promises to be resolved.
function parseJSON() {
var deferred = $q.defer();
var promiseList = [];
for (var i = 0; i < guideline.intersections.length; i++) {
promiseList.push(getKeywordInCategory(guideline.line_id, snippets.keyword));
promiseList.push(getKeywordInCategory(guideline.intersections[i].line_id, snippets.keyword));
}
$q.all(promiseList).then(function() {
deferred.resolve();
});
return deferred.promise;
} //End ParseJSON

Categories

Resources