Hopefully someone can help me correct the below code.
I am not able to get anything in the final return statement.
I know it looks messy as I have tried a few alternatives.
async function concatnames(allSelectedRows){
var flightName = [];
var name = "";
var resultOutCome = "";
try {
var result = await allSelectedRows.forEach(
function (selectedRow, i){
recordidGuid = selectedRow.getData().getEntity().getEntityReference().id.replace('{', '').replace('}', '') //Flight Plan Event Guid
name = Xrm.WebApi.online.retrieveRecord("crd80_flightplanevent", recordidGuid, "?$select=_crd80_learnerflight_value")
})
resultOutCome = name["_crd80_learnerflight_value#OData.Community.Display.V1.FormattedValue"]
console.log(resultOutCome)
flightName.push(resultOutCome);
}
catch (error) {
DisplayError(error)
}
return flightName
}
await only works with promises, allSelectedRows.forEach(...) it's not a promise, it's a function.
You want to do something like this:
await Promise.all(allSelectedRows.forEach(
async function (selectedRow, i) {
recordidGuid = await selectedRow.getData().getEntity().getEntityReference().id.replace('{', '').replace('}', '') //Flight Plan Event Guid
name = await Xrm.WebApi.online.retrieveRecord("crd80_flightplanevent", recordidGuid, "?$select=_crd80_learnerflight_value")
}))
The Promise.all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will resolve when all of the input's promises have resolved, or if the input iterable contains no promises.
And the new allSelectedRows.forEach(...) with now async functions, returns promises, because when you do async function NameOfFunction, you are actually creating a promise, that Promise.all() will return all them resolved.
And I am using an await in Xrm.WebApi.online.retrieveRecord("crd80_flightplanevent", recordidGuid, "?$select=_crd80_learnerflight_value") because I think it is a promise, so when I add await, it will return me the resolved value of the promise.
Depending on what you to accomplish, there's a couple of ways to do this:
Process Each Request Sequentially
This is (a) slower than the alternative, and (b) more code. However, it gives you granular control of the error(s) encountered:
// process each request sequentially
async function concatnames( allSelectedRows ) {
const flightName = [];
let name = "";
let resultOutCome = "";
try {
for ( const selectedRow of allSelectedRows ) {
recordidGuid = selectedRow
.getData()
.getEntity()
.getEntityReference()
.id
.replace('{', '')
.replace('}', '');
name = await Xrm
.WebApi
.online
.retrieveRecord(
"crd80_flightplanevent",
recordidGuid,
"?$select=_crd80_learnerflight_value"
);
resultOutCome = name["_crd80_learnerflight_value#OData.Community.Display.V1.FormattedValue"];
flightName.push(resultOutCome);
}
}
catch (error) {
DisplayError(error)
}
return flightName
}
Run the requests in parallel
This is nice and clean. Also faster than the above because all request are run in parallel:
async function concatnames(allSelectedRows) {
const flightName = await Promise.all( allSelectedRows.map( row => {
recordidGuid = selectedRow
.getData()
.getEntity()
.getEntityReference()
.id
.replace('{', '')
.replace('}', '');
return Xrm.WebApi.online
.retrieveRecord(
"crd80_flightplanevent",
recordidGuid,
"?$select=_crd80_learnerflight_value"
)
.then( name => name["_crd80_learnerflight_value#OData.Community.Display.V1.FormattedValue"]);
}))
.catch( DisplayError );
return flightName || [];
}
Related
Suppose I have code like this:
let result = {
name: downloadNameFromInternetVerySlow(),
isFamous: determineIfNameIsFamous(this.name);
}
const downloadNameFromInternetVerySlow = () => {
path = "/home";
const folders = fs.readdirSync(path).filter(file => fs.lstatSync(path).isDirectory());
console.log(folders);
return folders[0];
}
downloadNameFromInternetVerySlow can take a long time, meanwhile determineIfNameIsFamous depends on downloadNameFromInternetVerySlow's result to return correct value.
How do I make sure determineIfNameIsFamous only runs after downloadNameFromInternetVerySlow is done?
The code you show is entirely synchronous so asynchronous results aren't actually the issue here like people were guessing. The issue is that this.name cannot be used to refer to a prior property in an object literal definition for several reasons, not the least of which this isn't set to the object you want.
Instead, you can do this:
let result = {};
result.name = downloadNameFromInternetVerySlow();
result.isFamous = determineIfNameIsFamous(result.name);
You can convert downloadNameFromInternetVerySlow to a Promise(if not already). and then user await to wait till it finished. Otherwise you can use Promise.then()
(This should be inside an async function)
let name = await downloadNameFromInternetVerySlow(),
let isFamous = determineIfNameIsFamous(this.name);
let result = {
name,
isFamous
}
Another method using Promise.then
let name, isFamous;
downloadNameFromInternetVerySlow().then(result => {
name = result;
isFamous = determineIfNameIsFamous(this.name);
});
Async/await
There’s a special syntax to work with promises in a more comfortable fashion, called “async/await”.
An async function returns a promise, like in this example:
const doSomethingAsync = () => {
return new Promise(resolve => {
setTimeout(() => resolve('I did something'), 3000)
})
}
When you want to call this function you prepend await, and the calling code will stop until the promise is resolved or rejected. One caveat: the client function must be defined as async. Here's an example :
const doSomething = async () => {
console.log(await doSomethingAsync())
}
function downloadNameFromInternetVerySlow(){
return new Promise(resolve => setTimeout(() => {
console.log("Set timeout resolving...");
return resolve("Sample Name");
}, 5000))
}
function determineIfNameIsFamous(name){
if(name === "Sample Name"){
return true;
}
return false;
}
async function myFunc(){
let result = {};
result.name = await downloadNameFromInternetVerySlow();
console.log(result);
result.isFamous = determineIfNameIsFamous(result.name);
return result;
}
myFunc().then(res => console.log("result : ", res))
Assuming downloadNameFromInternetVerySlow is asynchrononous (otherwise it would already work like you want), the solution is to use await.
Please note, to be able to do that, this code needs to be wrapped in an async function (or or top level with top level await available.)
async function getAndUseResult(){
let result = {
name: await downloadNameFromInternetVerySlow(),
isFamous: determineIfNameIsFamous(this.name)
}
console.log(JSON.stringify(result))
}
getAndUseResult();
I am using it many places in my code and it works. but at one place it didn't give any error, also did not give me the desired result. and when I show my code to the support forum they suggest that "You are using the JS object/class “Promise” incorrectly."
Can Anyone guide me on what's wrong with my code
here is my code sample:
let charity = {};
await Promise.all(
charity = charityData.map(function( data ) {
let address = data.zipCode
let url = "https://maps.googleapis.com/maps/api/geocode/json?&address="+`'${address}'`+"&key=***Google geocoding Key***"; //client's Key
let urlResponse = Backendless.Request.get(url)
// let latitude = urlResponse.results[0].geometry.location.lat;
// let longitude = urlResponse.results[0].geometry.location.lng;
//let updateCharitiesData = {'objectId': data.objectId, 'latitude':latitude, 'longitude':longitude};
return urlResponse;
})
);
return charity;
Almost. Assuming Backendless.Request.[method] returns a promise it would be more correct to do something along the lines of:
async function getCharityData() {
const charity = await Promise.all(charityData.map( async function(data) {
const address = data.zipCode;
const url =
`https://maps.googleapis.com/maps/api/geocode/json?&address=${address}&key=***Google geocoding Key***`; //client's Key
const urlResponse = await Backendless.Request.get(url);
return urlResponse;
}));
return charity
}
Promise.all requires an array as its argument to work correctly; passing an Array.map here and assigning the returned value to charity both ensures your Promise.all runs as expected and the returned array is an array of resolved promises.
I would do it like this:
function getCharityData() {
// `charity` is an array of Promises that will each resolve to
// a response.
const charity = charityData.map((data) => {
let address = data.zipCode;
let url = 'https://maps.googleapis.com/maps/api/geocode'
let urlResponse = Backendless.Request.get(url);
return urlResponse;
});
return Promise.all(charity);
}
try {
const charityData = await getCharityData();
} catch (e) {
console.error(e);
}
This way, charityData will be an array of fetched responses.
In your code, the result of Promise.all() is never assigned to charity before it's returned, and that is the value you want.
If you have access to Async/Await I'd simply do the following:
function getCharityData(charityData) {
let results = [];
for (let i = 0; i < charityData.length; i++) {
let url = `https://maps.googleapis.com/maps/api/geocode/json?&address=${charityData[i].zipCode}&key=***Google geocoding Key***`;
try {
let result = await Backendless.Request.get(url);
results.push(result);
} catch (err) {
console.log("Oh dear!");
}
}
return results;
}
For your use case, there's no need to use any Promise libraries when you have Async/Await, good old fashioned for loops and await (I'd personally prefer to do this sort of call sequentially instead of in parallel like Promise.all implores when I'm querying external APIs. This also ensures we don't fail fast like Promise.all does.).
I'm struggling a bit with JS promises.
I am using a library to pull data from Spotify that returns promises.
In my main function I can use an await to build an object from the response data and push it to an array (called nodes):
var nodes = [];
main();
async function main() {
var id = '0gusqTJKxtU1UTmNRMHZcv';
var artist = await getArtistFromSpotify(id).then(data => buildArtistObject(data));
nodes.push(artist);
When I debug here then all is good, nodes has my object.
However, when I introduce a 2nd await underneath to make another call:
nodes.forEach((node, i) => {
if (node.done == false) {
console.log(node.toString());
var related_artists = await getRelatedArtists(node.spotify_id);
I get the following error: SyntaxError: await is only valid in async function
I thought the first await statement would be resolved and the execution would continue until the next..?
Any help would be greatly appreciated.
EDIT
The other functions, if that helps, are just as follows:
function getArtistFromSpotify(id) {
let response = spotify
.request('https://api.spotify.com/v1/artists/' + id).then(function (data) {
return data;
})
.catch(function (err) {
console.error('Error occurred: ' + err);
return null;
});
return response;
}
function getRelatedArtists(id) {
let response = spotify
.request('https://api.spotify.com/v1/artists/' + id + '/related-artists').then(function (data) {
return data;
})
.catch(function (err) {
console.error('Error occurred: ' + err);
return null;
});
return response;
}
function buildArtistObject(data) {
var artist = {
node_id: nodes.length,
name: null,
genres: null,
popularity: null,
spotify_id: null,
done: false
}
artist.name = data.name;
artist.genres = data.genres;
artist.popularity = data.popularity > 0 ? data.popularity : 0;
artist.spotify_id = data.id;
return artist;
}
The code below has multiple problems.
var nodes = [];
main();
async function main() {
var id = '0gusqTJKxtU1UTmNRMHZcv';
var artist = await getArtistFromSpotify(id).then(data => buildArtistObject(data));
nodes.push(artist);
// ...
First of all, main mutates global scope nodes. Not only is this an antipattern even in synchronous code (functions should not rely on, or modify, global variable names; use parameters and return values instead), in asynchronous code, nodes will never be available for use anywhere but within main. See How do I return the response from an asynchronous call?.
Secondly, try to avoid combining then and await. It's confusing.
It's also a little odd that an array of nodes is used, yet only one artist is pushed onto it...
As for this code:
nodes.forEach((node, i) => {
if (node.done == false) {
console.log(node.toString());
var related_artists = await getRelatedArtists(node.spotify_id);
// ...
The error is self-explanatory. You must add async to the enclosing function if you want it to be asynchronous: nodes.forEach(async (node, i) => { // .... But that spawns a new promise chain per node, meaning future code that's dependent on the result won't be able to await all of the promises in the loop resolving. See Using async/await with a forEach loop. The likely solution is for..of or Promise.all.
While I'm not 100% sure what your final goal is, this is the general pattern I'd use:
async function main() {
const id = '0gusqTJKxtU1UTmNRMHZcv';
const data = await getArtistFromSpotify(id);
const artist = await buildArtistObject(data);
const nodes = [artist]; // odd but I assume you have more artists somewhere...
for (const node of nodes) {
if (!node.done) {
const relatedArtists = await getRelatedArtists(node.spotify_id);
}
}
/* or run all promises in parallel:
const allRelatedArtists = await Promise.all(
nodes.filter(e => !e.done).map(e => getRelatedArtists(e.spotify_id))
);
*/
// ...
}
main();
Since your code isn't runnable and some of the intent is unclear from the context, you'll likely need to adapt this a bit, so consider it pseudocode.
You have some misunderstandings of how to use promises -
let response = spotify
.request(url)
.then(function(data) { return data }) // this does nothing
.catch(function (err) { // don't swallow errors
console.error('Error occurred: ' + err);
return null;
})
return response
You'll be happy there's a more concise way to write your basic functions -
const getArtist = id =>
spotify
.request('https://api.spotify.com/v1/artists/' + id)
const getRelatedArtists = id =>
spotify
.request('https://api.spotify.com/v1/artists/' + id + '/related-artists')
Now in your main function, we can await as many things as needed. Let's first see how we would work with a single artist ID -
async function main(artistId) {
const artistData = await getArtist(artistId)
const relatedData = await getRelatedArtists(artistId)
return buildArtist(artistData, relatedData)
}
If you have many artist IDs -
async function main(artistIds) {
const result = []
for (const id of artistIds) {
const artistData = await getArtist(artistId)
const relatedData = await getRelatedArtists(artistId)
result.push(buildArtist(artistData, relatedData))
}
return result
}
Either way, the caller can handle errors as
main([693, 2525, 4598])
.then(console.log) // display result
.catch(console.error) // handle errors
Which is the same as -
main([693, 2525, 4598]).then(console.log, console.error)
The pattern above is typical but sub-optimal as the caller has to wait for all data to fetch before the complete result is returned. Perhaps you would like to display the information, one-by-one as they are fetched. This is possible with async generators -
async function* buildArtists(artistIds) {
for (const id of artistIds) {
const artistData = await getArtist(artistId)
const relatedData = await getRelatedArtists(artistId)
yield buildArtist(artistData, relatedData) // <- yield
}
}
async function main(artistIds) {
for await (const a of buildArtists(artistIds)) // <- for await
displayArtist(a)
}
main([693, 2525, 4598]).catch(console.error)
I am running this function with NodeJS, but the process never stops running, despite the fact that I am returning a value once all promises have been resolved.
How can I modify this code so the function terminates when it's done?
let Parser = require('rss-parser');
let parser = new Parser();
var sources = {
feed1: 'http://www.example.url/feed',
feed2: 'http://www.another.url/feed'
}
(async() => {
var promises = [];
for (var feedName in sources) {
if (sources.hasOwnProperty(feedName)) {
const result = parser.parseURL(sources[feedName])
promises.push(result)
}
}
return await Promise.all(promises)
})()
You can take a simple approach handling promises in one array to catch them after parsing each url. Something like this:
let Parser = require('rss-parser');
let parser = new Parser();
var sources = {
feed1: 'http://www.example.url/feed',
feed2: 'http://www.another.url/feed'
}
function parsePromise(url) {
return new Promise((resolve,reject) => {
let data = parser.parseURL(url);
resolve(data);
});
}
// main
toread = [];
for (var feedName in sources) {
let url = sources[feedName];
let p = parsePromise(url);
toread.push(p);
}
Promise.all(toread).then((data) => {
console.log(data);
});
First, I think your async arrow function is incorrect. You are not actually invoking it properly. See the syntax for async arrow functions in this question: Syntax for async arrow function.
Since you are already trying to do an immediate anonymous function, just use the function declaration instead of arrows.
Additionally, you are trying to return something from an anonymous, immediately invoked function and you aren't doing anything with the returned value.
Not only that, but you are awaiting the Promise.all() only to have whatever you waited on get wrapped in a Promise anyway since async functions return Promises no matter what.
Second, use more map()s or intermediate values so you program flows more clearly. The benefit of await is that you can write what looks like synchronous code, but it will be handled in an asynchronous manner.
const Parser = require("rss-parser"),
parser = new Parser();
const sources = {
feed1: "https://sports.yahoo.com/nhl/teams/bos/rss.xml",
feed2: "https://sports.yahoo.com/nhl/teams/buf/rss.xml"
};
(async function () {
let data
try {
const sourcesArr = Object.keys(sources).map(k => sources[k]),
feedPromises = sourcesArr.map(source => parser.parseURL(source));
data = await Promise.all(feedPromises);
} catch (err) {
console.error(err);
}
console.log(data);
}());
I am using lodash library for iteration. I have used _.map() function for this purpose. I have a problem that I have an asynchronous function inside _.map(). My code is like this:
let dataToRender = {};
let modifiedData = _.map(myArray, (element) => {
dataToRender.uuid = element.UID;
dataToRender.pendingAmountReceived = element.$.SR;
dataToRender.orderID = element.TransactionID;
dataToRender.orderAmount = element.TransactionValue;
dataToRender.orderDate = moment(element.TransactionTime, 'YYYY-MM-DDTHH:mm:ss');
dataToRender.goal = element.Product.replace('amp;', '&');
dataToRender.currentStatus = 'waiting';
//Here CheckFilter is my asynchrounous function
//I have to do calculation base on the response of this checkFilter
checkFilter(requireParams)
.then(data => {
//If the response returned from checkFilter is false I have to include this data
if (!data) {
return requireParams;
}
});
});
res.json(modifiedData);
But I am getting just null in modifiedData. How to get all the data?
Bluebird provides a number of promise helper methods including Promise.map() which will only resolve when all returned promises are resolved.
The only problem is you will end up with undefined values in your results for when the checkFilter function is truthey. lodash's _.compact function can help here or you can use _.filter if you have more specific filtering needs.
const Promise = require('bluebird');
let dataToRender = {};
Promise.map(myArray, element => {
dataToRender.uuid = element.UID;
dataToRender.pendingAmountReceived = element.$.SR;
dataToRender.orderID = element.TransactionID;
dataToRender.orderAmount = element.TransactionValue;
dataToRender.orderDate = moment(element.TransactionTime, 'YYYY-MM-DDTHH:mm:ss');
dataToRender.goal = element.Product.replace('amp;', '&');
dataToRender.currentStatus = 'waiting';
return checkFilter(requireParams)
.then(data => {
// If the response returned from checkFilter is false I have to include this data
if (!data) return requireParams;
});
})
.then(results => res.json(_.compact(results)) );