Thanks for viewing my question. I am hopeful that you might help me go in the right direction.
Problem:
User sends a list of company name and server sends the corresponding logo back.
The restriction is that the logo needs to be placed left to right and then top to bottom.
The order should not change.
Approach Followed:
We are using an Array of Company Names appearing row wise -
Like:
[ [Companies_Row1] , [Companies_Row2], [Companies_Row3] ]
Each [Companies_Row] has values like [‘Ford’, ‘BMW’, ‘Audi']
[
We expect the logos to appear in order, however presently we are getting them in random order.
Solution we are thinking is to iterate over the arrays and make use of Promises.
However we are struggling to implement this since last two days. Please help us resolve the same.
Code Snippets:
// arrayOfCompanyNames - contains an Array of [Array of Company Names] appearing in a row. Please Refer Screenshot.
// Function where we are traversing the company Name and making call
function getCompanyLogos(arrayOfCompanyNames) {
var promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise.then(function(){
arrayOfCompanyNames.forEach(function(item) {
item.forEach(function(value, index) {
if(index == item.length - 1){
// When a row is complete then move to the next row.
promise = promise.then(LogoFetchPromis(value, true));
} else {
promise = promise.then(LogoFetchPromis(value));
}
});
});
});
}
function LogoFetchPromis(carName, wrapNow){
return new Promise(function(resolve, reject) {
$.ajax({
url: "/getLogo/" + carName,
dataType: 'text'
})
.done(function(response) {
resolve(response);
// logos is the section where logos should appear
$('.logos').append(response);
if (wrapNow)
$('.logos').append('<br>');
})
.fail(function(response) {
reject(Error(response));
});
});
}
Please help us find a resolution for the same.
Keep it simple. One solution is first fetch all of your images by building an array of promises reproducing the structure of arrayOfCompanyNames then use Promise.all() to keep the order of the promises result. And then you can loop over the results array and show the logos (add error management as you need).
// arrayOfCompanyNames - contains an Array of [Array of Company Names] appearing in a row. Please Refer Screenshot.
var arrayOfCompanyNames;
// Function where we are traversing the company Name and making call
function getCompanyLogos(arrayOfCompanyNames) {
// build an array of promises mirroring arrayOfCompanyNames
var promises = [];
arrayOfCompanyNames.forEach(function (subArray) {
var subPromises = [];
subArray.forEach(function (carName) {
// $.ajax is a promise so you use it directly
subPromises.push($.ajax({
url: '/getLogo/' + carName,
dataType: 'text'
}));
});
promises.push(Promise.all(subPromises));
});
// show everything, results is nicely formatted so you know when to <br>
return Promise.all(promises).then(function done(results) {
// results look like [[row1], [row2], [row3]]
results.forEach(function (row) {
row.forEach(function (carLogo) {
$('.logos').append(carLogo);
});
$('.logos').append('<br>');
});
})
}
The Promise.all() solution will introduce a delay, the logos will show at once when they're all fetched. They will be downloaded all at the same time so be careful if it's not your server you're requesting (almost 100 concurrent requests is consequent).
The other way is what you started, building a promise chain.
// arrayOfCompanyNames - contains an Array of [Array of Company Names] appearing in a row. Please Refer Screenshot.
var arrayOfCompanyNames;
// Function where we are traversing the company Name and making call
function getCompanyLogos(arrayOfCompanyNames) {
// this is enough to create a 'base' resolved promise
var promise = Promise.resolve();
arrayOfCompanyNames.forEach(function (item) {
item.forEach(function (value, index) {
if (index == item.length - 1) {
// When a row is complete then move to the next row.
promise = promise.then(LogoFetchPromis.bind(null, value, true));
} else {
promise = promise.then(LogoFetchPromis.bind(null, value));
}
});
});
return promise;
}
function LogoFetchPromis(carName, wrapNow) {
// don't create a new Promise here, $.ajax is already a promise
return $.ajax({
url: "/getLogo/" + carName,
dataType: 'text'
}).done(function (response) {
// logos is the section where logos should appear
$('.logos').append(response);
if (wrapNow)
$('.logos').append('<br>');
}).fail(function (response) {
// manage error here is needed
});
}
I commented the changes but the most important is promise.then(LogoFetchPromis(value));. You're calling LogoFetchPromis() directly, that's why your logos show randomly, all the $.ajax are actually done at once. In the fixed code (promise.then(LogoFetchPromis.bind(null, value)), bind() returns a Function that is not executed until promise is resolved so LogoFetchPromis() will be called one at a time and your logos should be displayed in order.
Related
I'm struggling to send multiple AJAX calls in the following task. The API returns takes two parameters: userId and offsetValue and returns last 10 messages for the specified user, starting from specified offset. If the offset is more than the total count of messages for the user, API returns an empty string.
I wrote a function that returns an individual promise to get 10 messages for specified userId and offsetValue.
function getMessages(userId, offsetValue) {
return new Promise(function (resolve, reject) {
$.ajax(
{
url: 'https://example.com/api.php',
type: 'POST',
data: {
action: 'get_messages',
offset: offsetValue,
user: userId
},
success: function (response) {
if (response != '') {
resolve(response);
} else {
reject(response);
}
},
error: function (response) {
reject(response);
}
});
});
}
I need to run parallel tasks using .all() for multiple userId, however I cannot run parallel subtasks for each userId (incrementing offsetValue by 10 each time) as I don't know in advance how many messages does each user have, so the execution should stop when first individual promise is rejected (i.e. offsetValue is more than total messages count). Something like this:
var messages = '';
getMessages('Alex', 0)
.then(function(result) {
messages += result;
getMessages('Alex', 10);
.then(function(result) {
messages += result;
getMessages('Alex', 20)
....
});
});
So, is there any way to run sequental promises with iterating parameter sequentially one by one and resolve the overall concatenated result upon first reject?
First off, you want to avoid the promise anti-pattern where you unnecessarily wrap your code in a new promise when $.ajax() already returns a promise that you can just use. To fix that, you would change to this:
// retrieves block of messages starting with offsetValue
// resolved response will be empty if there are no more messages
function getMessages(userId, offsetValue) {
return $.ajax({
url: 'https://example.com/api.php',
type: 'POST',
data: {
action: 'get_messages',
offset: offsetValue,
user: userId
}
});
}
Now, for your main question. Given that you want to stop requesting new items when you get a rejection or empty response and you don't know how many requests there will be in advance, you pretty much have to request things serially and stop requesting the next one when you get an empty response or an error. The key to doing that is to chain sequential promises together by returning a new promise from a .then() handler.
You can do that like this:
function getAllMessagesForUser(userId) {
var offsetValue = 0;
var results = [];
function next() {
return getMessages(userId, offsetValue).then(function(response) {
// if response not empty, continue getting more messages
if (response !== '') {
// assumes API is returning 10 results at a time
offsetValue += 10;
results.push(response);
// chain next request promise onto prior promise
return next();
} else {
// empty response means we're done retrieving messages
// so just return results accumulated so far
return results.join("");
}
});
}
return next();
}
This creates an internal function that returns a promise and each time it gets some messages, it chains a new promise onto the original promise. So, getAllMessagesForUser() returns a single promise that resolves with all the messages it has retrieved or rejects with an error.
You would use it like this:
getAllMessagesForUser('Bob').then(function(messages) {
// got all messages here
}, function(err) {
// error here
});
You could parallelize multiple users (as long as you're sure you're not overloading the server or running into a rate limiting issue) like this:
$.when.apply($, ['Bob', 'Alice', 'Ted'].map(function(item) {
return getAllMessagesForUser(item);
})).then(function() {
// get results into a normal array
var results = Array.prototype.slice.call(arguments);
});
P.S. Promise.all() is much nicer to use than $.when() (since it takes an array and resolves to an array), but since there are already jQuery promises involved here and I didn't know your browser compatibility requirements, I stuck with jQuery promise management rather than ES6 standard promises.
I have the following function, my goal is to push to the items list, when the components can identify their parent item.
The problem that I have is that when I am pushing to the list console.log() shows me that the object is in there, but when I return the list and catch it in another function, there is nothing in the list.
I think that the items list is returned before the code above it is done.
private get_items_for_request() {
return this.Item.forRequest(this.request.id, ['group'])
.then((_items) => {
var items = [];
for (var item of _items) {
return this.ItemComponent.forItem(item.id, ['type'])
.then((_components) => {
for (var component of _components) {
if (component.type.can_identify_item) {
items.push({
group_id: item.group.reference,
identifier_code: this.remove_check_digit_if_necessary(
component.identifier_code),
quantity: 1
});
break;
}
}
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('components.load_failed'));
return [];
});
}
return items;
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('items.failed_load'));
return [];
});
}
I'm afraid the trouble is with your approach, not with the code itself. A promise is just that - a promise of data some time in future. So, when you function returns (immediately), the promise is not resolved yet and the data your caller function captures is still empty. I don't see console.log() in your code, but would suspect you put it somewhere inside then(), which will be called once data has been received. So you will see data being logged. Trouble is by that time get_items_for_request() has already returned and your caller function has already moved on.
If you want to use a promise, you have to use a callback to be called once promise is resolved. But if you want actual data returned to the caller you need to fetch data synchronously.
For the synchronous fetch, check this response. But beware that synchronous fetch will hurt responsiveness of your script.
For an asynchronous fetch (using promises), you need to define a callback called once all data has been fetched. I won't try to fix your code, but it should go along the following sketch in javascript. Though keep in mind it's just a sketch.
function get_items_for_request(onSuccess) {
var items = []
var total = -1
var sofar = 0;
this.Item.forRequest(this.request.id, ['group'])
.then(function (_items) {
var items = [];
total = _items.length // remember how many nested calls will be made
for (var item of _items) {
this.ItemComponent.forItem(item.id, ['type'])
.then(function (_components) {
// push received data to the items array here
sofar++
if (sofar == total) { // all done
onSuccess(items)
}
}
}
}
}
In the first promise callback, you have two returns, only the first one runs. You return a promise after the first for, which you resolve to undefined. You should wait for an array of promises, each corresponding to a this.ItemComponent.forItem call.
Promise.all helps with this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You should do something like this:
return this.Item.forRequest(this.request.id, ['group']).then((_items) => {
return Promise.all(_items.map(function (item) {
return this.ItemComponent.forItem(item.id, ['type']).then((_components) => {
for (var component of _components) {
if (component.type.can_identify_item) {
return {
group_id: item.group.reference,
identifier_code: this.remove_check_digit_if_necessary(
component.identifier_code),
quantity: 1
};
}
}
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('components.load_failed'));
return [];
});
}));
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('items.failed_load'));
return [];
} )
If you only want one item, find the first non-falsy element in the resulting array
What I want to do : get a list of projects (from API), request the status for each project. And call a callback who should represent the global status of my projects.
How I am trying to do it : the first promise get all projects and in the first then I am trying to call for each project a getProjectStatus who should get the status. I want that my second then wait that I finished the iteration (I tried many different things, but my second then is always called before). I don't know if this is a good using of promises...
var that = this;
var options = { url : this.host + "projects", json : true }
var finalStatus = "SUCCESS";
// first promises
return request(this.options)
.then(function(projects) {
projects.forEach(function(element, index, array) {
// second promises
that.getProjectStatus(element.id, function(status){})
.then(function(status) {
if(status != "SUCCESS)
{
finalStatus = "FAILURE";
}
});
})
})
.then(function(){
callback(finalStatus)});
This should get you going. You just use Promise.all on the array of second promises. I may not have understood the exact syntax of getProjectStatus but you can adjust I hope if needs be
return request(this.options)
.then(function(projects) {
return Promise.all( projects.map(element => that.getProjectStatus(element.id) )
.then(function(){
callback(finalStatus)});
I'm trying to figure out Promises with Parse.
What I want to do is get a lot of jobs, and then perform an update for each job.
var queryJobs = new Parse.Query("Jobs");
queryJobs.find().then(function (results) {
// Loop thorugh all jobs
for (var i = 0; i < results.length; i++) {
var job = results[i];
// Here. I want to run an update on all object and then continue.
}
return ????;
}).then(function () {
status.success("Finish");
}, function () {
status.error("Error");
});
I tried this without luck. The push block is never executed.
var queryJobs = new Parse.Query("Jobs");
queryJobs.find().then(function (results) {
var promises = [];
// Loop thorugh all jobs
for (var i = 0; i < results.length; i++) {
var job = results[i];
promises.push((function () {
// This is never executed.
var promise = new Parse.Promise();
var query = new Parse.Query("Jobs");
query.first({
success: function (object) {
// ... Do something here....
promise.resolve();
},
error: function () {
promise.resolve();
}
});
promise.resolve();
return promise;
}));
}
return Parse.Promise.when(promises);
}).then(function () {
status.success("Finish");
}, function () {
status.error("Error");
});
Thanks in advance
UPDATE
I've changed the code, and I get into the callback, however, the query is not executed.
...
promises.push((function () {
// GET HERE
var promise = new Parse.Promise();
var query = new Parse.Query("Jobs");
query.first({
success: function (object) {
console.log("CALLBACK");
promise.resolve();
},
error: function () {
console.log("CALLBACK");
promise.resolve();
}
});
}()));
return Parse.Promise.when(promises);
Here is how I would set this up:
var failure = new Parse.Promise();
var success = new Parse.Promise();
var queryJobs = new Parse.Query("Jobs");
queryJobs.each
(
function( job )
{
//Do your changes to the job
return job.save().then
(
function( job )
{
return Parse.Promise.as( "job saved" );
},
function( error )
{
failure.reject("There was an error trying to save a job: " + error.message);
return failure;
}
);
}
).then
(
function( results )
{
success.resolve("Successfully updated all the jobs" )
return success;
},
function( error )
{
failure.reject("There was an error trying to query for Jobs: " + error.message);
return failure;
}
).then
(
function( success )
{
response.success( success );
},
function( failure )
{
response.error( failiure );
}
);
This may not work out of the box, but it has a few key features that may help you.
1) I know that one of the perks that is mentioned in the blog post and what not announces promises is that you can get rid of pyramid code, but if you want descriptive error messages, the pyramid code is a necessary evil. My first promise (queryJobs.each in this case) always has two .then()'s. The second one always just does response.error( failure ) and response.success( success ).
2) I create two promises, although you can use just one. I prefer two so it is clear where I'm failing / succeeding. I return these where I reach a dead end/ the finish line.
3) I used query.each instead of query.find. query.find() is limited to 1000 results, which, while it will probably be more than enough for a long time, will eventually cause you to hit your limit, and you'd need to start paginating your results. Using query.each will perform your function on every single object that could be returned by the query. One perk of query.each vs query.find and iterating through the results is that query.each performs it's callback on each object asynchronously, rather than a linear iteration.
4) In this case it would probably be better just to have return job.save() inside of the each block, but I wanted to show how I do the nested promise returns. This is what allows me to have very specific success / error statements. This is important because even if one link in your promise chain fails, you will keep executing the next links. The exception to this is if a promise is rejected and you don't have an error function until your last chain. The error will get passed from link to link until it finds an error function, which is fine, except it limits how much you can customize your error messages.
I'll also note that what you have is probably going to return the same object again and again for that query.first() method, rather than working with the specific job from the first query. Like, you are iterating through your jobs, but instead of doing anything with each job, you're getting the first job and doing something with it again and again. I don't think that's what you actually wanted, but maybe this is meant to be a "learn promises" post rather than something functional.
Anyway, hope I helped a bit. Let me know if you have questions and I'll do my best to answer them.
edit: I know my style varies greatly from others'. I like opening and closing brackets on a new line, for the most part. I actually read in javascript that this can sometimes cause errors. I forget the specific cases, but this is not one of them. But feel free to edit the style back to how you prefer it.
You have to add promises to promises, not functions. You need to call the function so that it returns the promise:
promises.push((function () {
// ...
}()));
// ^^
Furthermore you have to remove the promise.resolve(); call before the return statement. The promise should only be resolved after the query succeeded. As it currently is, the promise is resolved immediately.
I'm working on a college/personal project which takes RSS urls and sends them through a series of APIs. Code follows:
var tempStory = [];
function getFeed () {
$.getJSON('spoonfed/app/scripts/categories.json', function(categories){
for (var category in categories){
if (categories[category][2] === true){
getFeedURLs(categories[category][0]).then(function(rssData) {
for (var item in rssData){
tempStory.push(rssData[item]);
}
});
}
}
});
}
There are an unknown number of categories I need to iterate over in addition to 10 news stories in each category, which are then pushed to the array tempStory. I need to run another function on this array of data. I think promises are the key here but I can't get my head around how I'd use them in this case.
Any help would be really appreciated and I'm happy to provide more of the code if needed, including the structure of the categories.json file and the getFeedURLs function.
If the issue is that you're just trying to figure out how to get all the data out, then you can do this:
function getFeed() {
return $.getJSON('spoonfed/app/scripts/categories.json').then(function (categories) {
var tempStory = [], promises = [];
for (var category in categories) {
if (categories[category][2] === true) {
promises.push(getFeedURLs(categories[category][0]).then(function (rssData) {
for (var item in rssData) {
tempStory.push(rssData[item]);
}
}));
}
}
return $.when.apply($, promises).then(function() {
// return results array as the fulfilled value of the promise
return tempStory;
});
});
}
getFeed().then(function(data) {
// all the data available here
}, function(err) {
// error here
});
The key aspects of this are:
Return the original ajax promise from getFeed().
When iterating the categories, push all those promises into an array to collect them all
Use $.when() with your array of promises to know when all of those are done
From within the $.getJSON.then() handler, return the $.when() promise which will chain to the $.getJSON() promise and let you return an array of results.
From within the $.when().then() handler, return our results so that becomes the fulfilled value of the final promise
Note: because of the way you chose to accumulate the tempStory array, it's results are not in a guaranteed order. Additional code could be written to maintain the order.