I have the following code with two functions that get executed asynchronously
products = [];
categories = [];
getProduct = function() {
productService.list().then(function(result) {
products = ["product1", "product2"]
});
}
getcategories = function() {
categoryService.list().then(function(result) {
categories = ["category1", "category2"]
});
}
i want another function that accept the two parameters, products and categories, something like that:
all = function(products, categories) {
console.log(products, categories);
}
but of course because of the async function, i'll get undefined. is there anyway to get the data after the previous functions get executed. m using old javascript so i can't use asynch await .
You can use promises chaining, call one by one,
see:
products = [];
categories = [];
productService.list()
.then(function(result) {
products = ["product1", "product2"]; // products = result;
return categoryService.list();
}).then(function(result) {
categories = ["category1", "category2"]; // categories = result;
all(products, categories);
}).catch(function(ex) {
// Error handele
}) ;
see exmple here
one more option is to use Promise.all
it can fit your use case as you don't need to read the functions one by one. in this option you should do something like:
Promise.all([productService.list(), categoryService.list()]).then(function(values) {
all(value[0],value[1]);
});
ES6 Solution with Promise.all:
EDIT For shortest solution:
Promise.all([productService.list(), categoryService.list()]).then(values => all(...values));
Previous Solution:
getProduct = function() {
return new Promise(res => {
productService.list().then(function(result) {
res(["product1", "product2"]);
});
});
}
getcategories = function() {
return new Promise(res => {
categoryService.list().then(function(result) {
res(["category1", "category2"]);
});
});
}
Promise.all([getProduct(), getcategories()]).then(aResolveValues => {
aResolveValues[0] //Has the values of the first resolve promise (products)
aResolveValues[1] //Has the values of the second resolve promise (categories)
});
You could chain if your situation suits it, or depending on your version of js (you don't mention what version you're targeting) you can either use Promise.all or fallback to a basic callback, e.g:
var products, categories,
all = function () {
if (products && categories) {
console.log(products, categories);
}
},
getProducts = function (oncomplete) {
return productService.list().then(function(result) {
products = ["product1", "product2"];
oncomplete();
});
},
getCategories = function (oncomplete) {
return categoryService.list().then(function(result) {
categories = ["category1", "category2"];
oncomplete();
});
};
getProducts(all) && getCategories(all);
Related
Consider the code:
const listOfEmployees = .... // get list of employees from god knows where ...
async function incrementHoursEmps(listOfEmployees) {
listOfEmployees.map(async employeeId => {
const condition = { where : {employeeId} };
const options = { multi: true };
const values = { hoursWork: sequelize.literal('"hoursWork" + 1') };
ModelEmployees.update(values, condition , options)
.then(result => {
// ....
})
.catch(error => {
console.log(error);
});
});
}
await incrementHoursEmps(listOfEmployees);
Whenever I try to update multiple rows (or even just a single row) using the .update() method of Sequelize , the code never awaits and the .update() doesn't really update.
Is it even working or am I doing something wrong ?
Array.map is not async aware. If you want to achieve this kind of setup you need to use Array.map to convert your list to promises, then use Promise.all to await on all promises and return an array of the results.
const listOfEmployees = .... // get list of employees from god knows where ...
async function incrementHoursEmps(listOfEmployees) {
return listOfEmployees.map(employeeId => {
const condition = { where: { employeeId } };
const options = { multi: true };
const values = { hoursWork: sequelize.literal('"hoursWork" + 1') };
return ModelEmployees.update(values, condition, options);
});
}
await Promise.all(incrementHoursEmps(listOfEmployees));
I'm trying to find the best way to go about this service call where I retain all the data in a single object. The call returns an object that has a property of next_page_url. If there is a next_page_url the function should keep chaining. Since I don't know what the url is until the next call resolves I need to call these in order and resolve them in order. I'm also collecting data from each call. I haven't been able to figure out what the structure should be
what I have so far
getDataFromAllPages = (url) => {
waniKaniAxios.get(url).then(object => {
if(object.data.pages.next_url){
return this.getDataFromAllPages(object.data.pages.next_url.replace(waniKaniAxios.defaults.baseURL, ''));
}
});
}
getWanikaniData = () => {
this.getDataFromAllPages('/subjects?types=vocabulary').then(result => {
console.log(result);
});
}
Abstract away the wanikaniaxios.get in another function to make recursion clearer.
Here's my badly formatted code (don't know how SF editor works) , feel to ask any questions if you have any. Happy coding.
getWanikaniData = () => {
this.getDataFromAllPages("/subjects?types=vocabulary")
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err); // always put a .catch when you're using prmomises.
});
};
getDataFromAllPages = async (url) => {
// using async await;
try {
let arr = []; // i am assuming you'll improve upon what data structure you might want to return. Linked list seems best to me.
const object = await waniKaniAxios.get(url);
if (object.data.pages.next_url) {
const laterData = await this.getDataFromAllPages(
object.data.pages.next_url.replace(waniKaniAxios.defaults.baseURL, "")
);
arr = [...arr, ...laterData];
} else {
arr = [...arr, object];
}
Promise.resolve(arr);
} catch (err) {
Promise.reject(new Error(`Oops new wanikani error, ${err}`));
}
};
FINAL UPDATE
Using part of the answer below I managed to get it working. Had to partially give up on the recursion aspect because I didn't how to make the promise resolve into data
Here's the final solution that I came up with
getDataFromAllPages = async (url) => {
let results = {};
try {
//getting intial data
const initialData = await waniKaniAxios.get(url);
//using the intial data and mapping out the levels then saving it into results object
results = this.mapOutLevels(initialData.data, results);
//get the next page url
let nextPageUrl = initialData.data.pages.next_url;
//while there is a next page url keep calling the service and adding it to the results object
while (nextPageUrl) {
const laterData = await waniKaniAxios.get(nextPageUrl);
nextPageUrl = laterData.data.pages.next_url;
results = this.mapOutLevels(laterData.data, results);
}
} catch (err) {
Promise.reject(new Error(`Opps new wanikani error, ${err}`));
}
return Promise.resolve(results);
};
getWanikaniData = () => {
this.getDataFromAllPages("/subjects?types=vocabulary")
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err);
});
};
I have the following structure on my firebase database:
I need to get the values of the keys pin. For that I'm working with a recursive function like this:
let pins = [];
const normalize = (snapchot) => {
snapchot.forEach(function(child) {
if(child.val().pin) {
pins.push(Promise.resolve(child.val().pin));
}
else normalize(child);
});
return Promise.all(pins);
}
And now, call the normalize function:
normalize(snapshot) // snapshot represents the data from the firebase db
.then(p => {
console.log(p); // [ 'mi-pin-agosto', 'mi-pin-julio' ]
})
.catch(err => {
// handle error
})
And it works, but when I debug that code, I see that return Promise.all(pins); gets called more than one time. I only need to be called only once, after the foreach have been completly finished; that's with the idea for the case of performance, because the snapshot data it's more large than the see it in the image I show.
Any ideas ???
to only use Promise.all once you can have the recursive function as a function "inside" `normalize
const normalize = (snapshot) => {
const process = x => {
let ret = [];
x.forEach(function(child) {
if(child.val().pin) {
ret.push(Promise.resolve(child.val().pin));
} else {
ret = ret.concat(process(child));
}
});
return ret;
});
return Promise.all(process(snapshot));
}
This code also doesn't require a global array to store the results
However, as there is nothing asynchronous about any of the code you are calling - dispense with the Promises inside normalize
const normalize = (snapshot) => {
let ret = [];
snapshot.forEach(function(child) {
if(child.val().pin) {
ret.push(child.val().pin);
} else {
ret = ret.concat(normalize(child));
}
});
return ret;
};
If you really have to use Promises for this code, you can simply
Promise.all(normalize(snapshot))
.then(p => {
console.log(p); // [ 'mi-pin-agosto', 'mi-pin-julio' ]
})
.catch(err => {
// handle error
})
After a bunch of looking into Futures, Promises, wrapAsync, I still have no idea how to fix this issue
I have this method, which takes an array of images, sends it to Google Cloud Vision for logo detection, and then pushes all detected images with logos into an array, where I try to return in my method.
Meteor.methods({
getLogos(images){
var logosArray = [];
images.forEach((image, index) => {
client
.logoDetection(image)
.then(results => {
const logos = results[0].logoAnnotations;
if(logos != ''){
logos.forEach(logo => logosArray.push(logo.description));
}
})
});
return logosArray;
},
});
However, when the method is called from the client:
Meteor.call('getLogos', images, function(error, response) {
console.log(response);
});
the empty array is always returned, and understandably so as the method returned logosArray before Google finished processing all of them and returning the results.
How to handle such a case?
With Meteor methods you can actually simply "return a Promise", and the Server method will internally wait for the Promise to resolve before sending the result to the Client:
Meteor.methods({
getLogos(images) {
return client
.logoDetection(images[0]) // Example with only 1 external async call
.then(results => {
const logos = results[0].logoAnnotations;
if (logos != '') {
return logos.map(logo => logo.description);
}
}); // `then` returns a Promise that resolves with the return value
// of its success callback
}
});
You can also convert the Promise syntax to async / await. By doing so, you no longer explicitly return a Promise (but under the hood it is still the case), but "wait" for the Promise to resolve within your method code.
Meteor.methods({
async getLogos(images) {
const results = await client.logoDetection(images[0]);
const logos = results[0].logoAnnotations;
if (logos != '') {
return logos.map(logo => logo.description);
}
}
});
Once you understand this concept, then you can step into handling several async operations and return the result once all of them have completed. For that, you can simply use Promise.all:
Meteor.methods({
getLogos(images) {
var promises = [];
images.forEach(image => {
// Accumulate the Promises in an array.
promises.push(client.logoDetection(image).then(results => {
const logos = results[0].logoAnnotations;
return (logos != '') ? logos.map(logo => logo.description) : [];
}));
});
return Promise.all(promises)
// If you want to merge all the resulting arrays...
.then(resultPerImage => resultPerImage.reduce((accumulator, imageLogosDescriptions) => {
return accumulator.concat(imageLogosDescriptions);
}, [])); // Initial accumulator value.
}
});
or with async/await:
Meteor.methods({
async getLogos(images) {
var promises = [];
images.forEach(image => {
// DO NOT await here for each individual Promise, or you will chain
// your execution instead of executing them in parallel
promises.push(client.logoDetection(image).then(results => {
const logos = results[0].logoAnnotations;
return (logos != '') ? logos.map(logo => logo.description) : [];
}));
});
// Now we can await for the Promise.all.
const resultPerImage = await Promise.all(promises);
return resultPerImage.reduce((accumulator, imageLogosDescriptions) => {
return accumulator.concat(imageLogosDescriptions);
}, []);
}
});
I want to update a list which is defined outside of a promise (API call).
here is the code:
let data = [];
api.getData(this.state.ID).then((resp) => {
data = [...data, { title : resp.title }];
data = [...data, { name : resp.name }];
});
console.log(data); // --> it returns []
the data is [ ] and no value have pushed to it. how can I change my code to fix this issue? for some reasons I don't want to use setState()
Regards.
Promise is ALWAYS asynchronous, meaning that your data array will still be empty after you define the promise.
To get access to filled data array you have to refer to it inside the promise.
let data = [];
api.getData(this.state.ID).then((resp) => {
data = [...data, { title: resp.title }, { name: resp.name }];
console.log(data);
});
Async/Await will help you:
let data = [];
async function getData() {
await api.getData(this.state.ID).then((resp) => {
data = [...data, { title : resp.title }];
data = [...data, { name : resp.name }];
});
console.log(data); // --> it shouldn't return empty array
}
getData();
Example:
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve("You see it after promis end");
}, 2000);
});
}
var arr = [];
async function f1() {
await resolveAfter2Seconds(10).then(res => arr.push(res));
alert(arr);
}
f1();
Your console.log function is getting executed before api returns you data. Although data got updated after api call. You could not see it because it is consoled before that.
let data = [];
api.getData(this.state.ID).then((resp) => {
data = [...data, { title : resp.title }];
data = [...data, { name : resp.name }];
Console.log(data)
})
Now you can see data consoles.
But if you want to console the data after then method then you can make the execution of api synchronized by using npm module q.