I am new to JavaScript and trying to understand how to write some code that makes a number of asynchronous calls to two third party API based on the contents of a previously generated input list.
Once all the data has been collected, I want to create a chart.
I have tried doing it a number of ways but from what I've read, using promises seems to be the recommended way. My brain is being twisted in knots, firstly using the traditional async model then trying to understand promises.
Can anyone point me in the right direction please?
var countries = [];
// countries ends up with 10-30 country names in previous code
var total_population = 0;
for(var n=0; n<countries.length; ++n) {
var country_data=new CountryData();
// async function that fetches country data
country_data.get(countries[n]);
country_data.onDone = function() {
var capital=this.capital;
var city_data=new CityData();
// async function that fetches city data
city_data.get(capital);
city_data.onDone = function() {
total_population += this.population;
}
}
}
// make a chart with total_population for example.
The general pattern I use in cases like this is to push all of my promises into an array, then set up a promise action that gets all the returned values and does what I want (console.log is your friend to see how the data is returned):
Edit: country_data.get must return a promise object for this to work.
var countries = ['US', 'CA', 'GB'];
var promises = [];
// countries ends up with 10-30 country names in previous code
for(var n=0; n<countries.length; ++n) {
var country_data=new CountryData();
// async function that fetches country data and returns a promise
promises.push(country_data.get(countries[n]));
}
//This will fire only once all async calls complete
Promise.all(promises).then(function(data) {
console.log(data);
//do stuff with data from all calls
}
Warning: I normally work with jquery which uses slightly different syntax, and jsfiddle is down so this isn't fully tested.
Assuming CountryData#get and CityData#get return promises that resolve the requested country/city data, this is how I would handle this:
var total_population = 0;
var promise = Promise.all(countries.map(function(country) {
return new CountryData().get(country)
.then(function(countryData) {
return new CityData().get(countryData.capital);
})
.then(function(cityData) {
total_population += cityData.population;
});
}));
edit: changed the solution to return a single promise
Related
I'm using a library called bugout (which is an API built on webtorrent) to create a P2P room but I need it to create the room based on a value from a table lookup using Dexie.
I know this has been rehashed a million times on Stack Overflow, but I'm still having trouble grasping the concept of promises or async await functions. I need the bugout operations to not proceed until this value is available. But I also don't want to get stuck in callback hell.
var room = db.profile.get(0, function (profile) {var ffname = profile.firstName;return ffname})
console.log(ffname) //returns undefined
var b = new Bugout(ffname);
I've also tried:
var room = db.profile.get(0, function (profile) {var ffname = profile.firstName;return ffname})
console.log(room) //returns undefined
var b = new Bugout(room);
How can I get ffname back with as little code as possible and not get stuck in a bunch of anonymous or asynchronous functions that will lock me out of the APIs?
The easiest and simplest way is this:
async function getBugoutFromId(id) {
var profile = await db.profile.get(id);
var ffname = profile.firstName;
console.log(ffname)
var b = new Bugout(ffname);
return b;
}
If you would prefer consuming promises without using async/await, do the following:
function getBugoutFromId(id) {
return db.profile.get(id).then(profile => {
var ffname = profile.firstName;
console.log(ffname)
var b = new Bugout(ffname);
return b;
});
}
The two functions works identically. Any of them returns a promise in its turn, so when you have called it. So whatever you are going to do with the retrieved Bugout instance, you need to handle the promise that your own function returns in the same manner as you did with the promise from Dexie.
async function someOtherFunction() {
var bugout = await getBugoutFromId(0);
console.log ("Yeah! I have a bugout", bugout);
}
or if you prefer not using async/await:
function someOtherFunction() {
return getBugoutFromId(0).then(bugout => {
console.log ("Year! I have a bugout", bugout);
});
}
I am trying to verify if a object already exists in a wix collection and if it does cancel the inset() call to the database
import wixData from "wix-data";
export function Memberships_beforeInsert(item, context) {
var name = item.firstName + item.lastName;
name = name.toLowerCase();
wixData.query(context.collectionName)
.find()
.then((res) => {
var members = res.items;
var len = res.length;
console.log(len)
for (var i = 0; i < len; i++) {
let member = members[i];
let memberName = member.firstName + member.lastName;
memberName = memberName.toLowerCase();
if (memberName === name) {
let toUpdate = {
'_id': member._id
}
wixData.update(context.collectionName, toUpdate)
return null;
}
return item;
}
});
//toHere
}
Im fairly new to and wixCode but I was expecting this to wait until the .then() is called then return as follows, but due to wixCode utilizing promises, the code goes immediately to the //toHere section of the code in which it dosent find a return and dismisses the call. Which adds the data to the database instead of returning null.
OK so when we look at your code it seems that you want to find a matching record to the one you are trying to insert to Memberships and then abort the insert and execute an update if one exists. The better way to do this is to look for the specific record match using the query .eq() function. If this finds a matching record then you can update otherwise continue with the insert. See below.
So quickly what is a promise?
In layman's terms think of a Promise like a FedEx tracking code.
When you call a promise based function to ask it to do something you are given back a Promise that what you asked for will be done or that you will be told if a problem occurred.
Just like a fed ex tracking code - you don't know if something has arrived (your function has done what you want it to) unless you check the tracking status using the code OR the fedex delivery van arrives and you receive the package. At which point the tracking code is updated to say the package was delivered and you might get a text or email saying it has been delivered.
A Promise function either completes successfully and you get a result in the then function call OR if an error occurs a catch() event is triggered. So All Promise based functions are similar to if then else conditional test only they look like this:
sendFedEx(package) // Call doesn't resolve immediately you have to wait for fedEx!
.then((fedExDeliveryInfo) => {
//Your package arrived!
})
.catch((fedExLostPackageInfo) => {
//Your package got lost :-(
});
In your code you do a case insensitive compare outside of the data collection. This is probably going to get resource intensive with a large data collection. A better way would be to store the case insensitive string:
(item.firstName + item.lastName).toLowerCase() in the data record and then use it for your query using .eq. That way you let the data collection do its job and simplify your code.
Note: This makes use of the fact that beforeInsert() returns a Promise.
Syntax:
function beforeInsert(item: Object, context: HookContext): Promise
Here is a modified suggestion for you with lots of comments!
import wixData from "wix-data";
export function Memberships_beforeInsert(item, context) {
// Memberships_beforeInsert returns a promise. So does
// wixData.query...find() so we simply return it to maintain the Promise
// chain.
var compareName = (item.firstName + item.lastName).toLowerCase();
// Add a compareName column value to item for future querying
// This will be inserted into the data collection
item.compareName = compareName;
//-------------------------------------------------------//
// This is the head of the Promise chain we need to return
//-------------------------------------------------------//
return wixData.query(context.collectionName)
.eq('compareName', item.compareName) // Query the compareName
.find()
.then((res) => {
var members = res.items;
var len = res.length;
console.log(len);
// Should only have one record or no records otherwise we have a
// problem with this code :-)
// So if we have too many we throw and error. This will be caught in
// an outer catch if we have one
if (len > 1) {
throw Error(`Internal Error! Too many records for ${item.firstName} ${item.lastName}`);
}
// If we get here we have a valid record OR we need to return a result
// To do this we will use a return variable set to the item which
// assumes we will insert item. This will be overridden with the save
// Promise if we have a record already
var result = item;
if (len === 1) {
// We have a record already so we need to update it and return null
// to the caller of beforeInsert to halt the insert. This is a
// Simple case of adding the _id of the found record to the item we
// have been given.
item['_id'] = member._id;
// Again remember we are using promises so we need to return the
// wixData.update result which is a promise.
result = wixData.update(context.collectionName, toUpdate)
.then((savedRecord) => {
// Now we have chained the update to the beforeInsert response
// This is where we can tell the Insert function to abort by
// returning null.
return null;
});
}
// Now we can return the result we have determined to the caller
return result;
});
}
This should do what you are trying to accomplish.
The response received is always an empty array. The inner stands_query in the for loop never gets executed. Also I would like to know if there is again an inner query inside the stands query then how do I achieve that.
STANDS CLASS STADIUMS CLASS Code below :
var final_list = [];
query.find().then(function(stadiums){
_.each(stadiums,function(stadium){
var stands_query = new Parse.Query(“Stands");
stands_query.equalTo(“stdId”,stadium.get(“stdId"));
var promise = stands_query.find().then(function(stands){
_.each(stands,function(stand){
var jsonObject = {
“stdId": stand.get(“stdId").id,
}
final_list.push(jsonObject);
});
return null;
},function(error){
return response.error(error);
});
return promise;
});
}).then(function(){
response.success(final_list);
});
Your first .then isn't returning anything. I'll break your code down so you can see it:
query.find().then(function(stadiums){ //anonymous function 1
_.each(stadiums,function(stadium){ //anonymous function 2
return "foo" //this returns "foo" as a result of anonymous function 2.
});
//Nothing explicitly returned from function 1!
}).then(function(){
response.success(final_list);
});
A function that lacks an explicit return statement will return undefined. Your code then executes "response.success" before any of the internal promises resolve.
What you could do instead is create an array of internal promises that you wait for with Parse.Promise.when:
query.find().then(function(stadiums){
var promises = [];
_.each(stadiums,function(stadium){
var promise = stands_query.find().then(...)
promises.push(promise);
});
//if returning another promise, the ".then" won't execute until it completes.
return Parse.Promise.when(promises);
}).then(function(){
response.success(final_list);
});
All this being said, you may run into timeout issues depending on how large your dataset is. Consider rewriting your query so that you query for Stands belonging to a Stadium with relational queries instead.
Update
Now that you've updated your question with fields, it looks like your line stands_query.equalTo(“stdId”,stadium.get(“stdId")); has two mistakes and will never return results. It should be stands_query.equalTo(“stadiumId”,stadium);.
We have many Stadiums, and every stadium has many Stands. The relationship between Stadium and Stand is represented in data by a pointer column on the Stands class called "stadiumId".
In comments, the functional goal is stated very simply: a JSON array of stands. This requires a single query, no looping at all:
function allTheStands() {
var query = new Parse.Query("Stands");
query.include("stadiumId");
return query.find().then(function(stands) {
return JSON.stringify(stands);
});
}
// call it like this:
allTheStands().then(function(jsonStands) {
// jsonStands is all of the stands represented as son
});
EDIT
A more roundabout way to the same result is to not include stadiumId in the query, instead doing a fetch after the stands query completes.
(This is just a specific form of advice given by #adamdport, given details of your data. You should credit his answer if you find this useful).
// adding underscorejs for handling arrays and other utils
var _ = require('underscore');
function allTheStands() {
var stands;
var query = new Parse.Query("Stands");
return query.find().then(function(result) {
stands = result;
// we're not done yet, because we need to fetch each stand's stadium
var promises = _.map(stands, function(stand) {
return stand.get("stadiumId").fetch().then(function(stadium) {
stand.set("stadiumId", stadium);
});
});
// as adamdport suggests, the crux of the looping answer is to use Promise.when()
return Parse.Promise.when(promises);
}).then(function() {
return JSON.stringify(stands);
});
}
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.
I understand using promises in simple scenarios but currently really confused on how to implement something when using a for loop and some updates to local sqlite database.
Code is as follows
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var q = $q.defer();
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
ajaxserviceAPI.postSurvey(survey).then(function(response) {
//from response update local database
surveyDataLayer.setLocalSurveyServerId(survey, response.result).then(function() {
q.resolve; // resolve promise - tried only doing this when last record also
})
});
})(survey) //pass in current survey used to pass in item into closure
}
return q.promise;
}).then(function() {
alert('Done'); // This never gets run
});
Any help or assistance would be appreciated. I'm probably struggling on how best to do async calls within loop which does another async call to update and then continue once completed.
at least promises have got me out of callback hell.
Cheers
This answer will get you laid at JS conferences (no guarantees though)
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
return Promise.all(Object.keys(surveys).map(function(key) {
var survey = surveys[key];
return ajaxserviceAPI.postSurvey(survey).then(function(response){
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
}));
}).then(function() {
alert('Done');
});
This should work (explanations in comments):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
// array to store promises
var promises = [];
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
var promise = ajaxserviceAPI.postSurvey(survey).then(function(response){
//returning this promise (I hope it's a promise) will replace the promise created by *then*
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
promises.push(promise);
})(survey); //pass in current survey used to pass in item into closure
}
// wait for all promises to resolve. If one fails nothing resolves.
return $q.all(promises);
}).then(function() {
alert('Done');
});
Awesome tutorial: http://ponyfoo.com/articles/es6-promises-in-depth
You basically want to wait for all of them to finish before resolving getSurveysToUpload, yes? In that case, you can return $q.all() in your getSurveysToUpload().then()
For example (not guaranteed working code, but you should get an idea):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var promises = [];
// This type of loop will not work in older IEs, if that's of any consideration to you
for (var item in surveys) {
var survey = surveys[item];
promises.push(ajaxserviceAPI.postSurvey(survey));
}
var allPromise = $q.all(promises)
.then(function(responses) {
// Again, we want to wait for the completion of all setLocalSurveyServerId calls
var promises = [];
for (var index = 0; index < responses.length; index++) {
var response = responses[index];
promises.push(surveyDataLayer.setLocalSurveyServerId(survey, response.result));
}
return $q.all(promises);
});
return allPromise;
}).then(function() {
alert('Done'); // This never gets run
});