I am trying to make api call and store the results in array. function itself is an async. Here's a code.
async function setPoliciesData(policies) {
let tmpPolicies = {};
await policies.forEach((policy) => {
const tmp = { ...policy };
// Here I need help. This returns promise Instade remembers How to wait till promise finishes
tmp["Name"] = getPolicyNameFromLocalStrage(policy.id);
try {
if (policy?.audience?.id) {
tmp["members"] = getMembersFromAudienceId(policy.audience.id);
} else {
tmp["members"] = [];
}
} catch (e) {
console.log(e);
}
let id = policy.id;
console.log("Setting policy ID : " + policy.id);
tmpPolicies[policy.id] = tmp;
});
console.log("Done the processing");
return tmpPolicies;
}
I am getting Promise object in return. I would want members returnd array.
I tried to console log and I am seeing that issue seems to be because of method is not async. What is proper way to fix it.
I refactored some of your code, but if you should make the function inside of the forEach asynchronous. In this case, I changed it to map to be a bit easier to follow. The key at the end is to return Promise.all() which will wait for all of the inner promises to be resolved before returning:
async function setPoliciesData(policies) {
const tmpPolicies = policies.map(async (policy) => {
policy.Name = await getPolicyNameFromLocalStrage(policy.id);
try {
policy.members = policy.audience && policy.audience.id
? await getMembersFromAudienceId(policy.audience.id)
: [];
} catch (e) {
console.error('Error: ' + e);
}
return policy;
});
console.log("Done the processing");
return Promise.all(tmpPolicies);
}
Related
I have an async function that returns an object after running fetch and .json(). I want to have more than one callback that take the object as input. Since I need to chain .then() to call the callback, I need to run the fetching function each time.
Is there a way to fetch once, and then have the callbacks do their thing with the output without having to refetch?
async function getData(){
const response = await fetch('api-url');
return await response.json();
}
function foo(data){
// do stuff
}
function bar(data){
// do stuff
}
const doFoo = getData().then(foo) // I don't want to run getData()
const doBar = getData().then(bar) // each time
I guess, I can save the output to a cache, but is there a way to do it with promises?
Any time you want to generate a value once and then use it multiple times: Store it in a variable.
const data = getData();
data.then(foo);
data.then(bar);
//to check the state of the promise
undefined === Promise.prototype.state && (Promise.prototype.state = function () {
const t = {};
return Promise.race([this, t]).then(v => (v === t) ? 'pending' : 'fulfilled', () => 'rejected');
});
function promiseWrapper(promiseFunction) {
let getPromise = Promise.reject();
let getFinalData = async function(force) {
let args = [].slice.call(arguments, 1);
//if force available, api will be called
if(force) {
return promiseFunction.apply(this, args);
}
let state = await this.getPromise.state();
//if state is rejected, trigger and fetch the data
'rejected' === state && (this.getPromise = promiseFunction.apply(this, args));
return this.getPromise;
}
//get method first method is force, rest will be treated as arguments
return {getPromise, get: getFinalData};
}
//instead of creating function pass the function in promiseWrapper
//promiseWrapper helps if somehow it is rejected, we wan recall the function without refreshing the page
var getData = promiseWrapper(async function() {
const response = await fetch('https://reqres.in/api/products/3');
return await response.json();
});
function foo(data) {
console.log('foo data\n', data)
}
function bar(data) {
console.log('bar data\n', data)
}
getData.get().then(foo) //It will trigger the api, because it is first time
getData.get(true).then(bar) //data by calling api
//getData.get(false).then(console.log) and getData.get().then(console.log), will act same
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 tried to prevent async problems with promises in the following code. By using a .then function everything within that function gets called after the function has been resolved. But now I have the problem that neither can I extend the scope of the ".then function" enough to include the bits after the second loop nor can I to my knowledge easily pause the code until the function has been properly resolved and THEN continue with the loop iteration.
Here's my main code(simplified):
let total = []
$.each(element, function(data) {
//Some other code
let out;
$.each(element2, function(data2) {
getZip(data2).then(function(txt){ //after everything has finished this get's called
out = someFunction(txt,data2);
total.push(out);
});
)};
console.log(total)//this gets called first
//some other code that does some stuff with total
)};
Here's the getZip code which is asynchronous:
function getZip(zipFile) {
return new Promise(function (resolve, reject){
zip = new JSZip()
JSZipUtils.getBinaryContent("someURL/" + zipFile, function (err, data) {
if (err) {
reject(err)
}
JSZip.loadAsync(data).then(function (zip) {
return zip.file(zipFile.replace(".zip", "")).async("text"); //gets the file within the zip andoutputs as text
}).then(function (txt) {
resolve(txt)
});
});
});
}
I'd be happy if either the getZip code could be made synchronous or if the before mentioned could be done.
I do not think I fully understand the code you have written. However, I recommend you use Promise.all. Here is an example I have written that I hope helps guide you:
let total = [];
$.each([1,2,3,4], function (data) {
// Some other code.
let out;
// Create a new promise so that we can wait on the getZip method.
new Promise(function (resolve, reject) {
// Create a holder variable. This variable with hold all the promises that are output from the getZip method you have.
let gZipPromises = [];
$.each([5,6,7,8], function (data2) {
// Your getZip method would go here. wrap the call to getZip in gZipPromises.push to push all the returned promises onto the holding variable.
gZipPromises.push(new Promise(function (resolve2, reject2) {
// Sample Code
setTimeout(function () {
total.push(data2);
resolve2("");
}, 10);
// End Sample Code.
}));
});
// Pass the holding variable to Promise.all so that all promises in the holding variable are executed before resolving.
Promise.all(gZipPromises).then(function() {
resolve()
});
}).then(function () {
// This will be called only when all getZip promises are completed in the second loop.
console.log(total);
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
With that said, I could not test your code. But I think this would work:
(Please note that based on the code you provided, the variable total would be logged for each iteration of the top most $.each
let total = []
$.each(element, function(data) {
//Some other code
let out;
// Define a new promise.
new Promise(function (resolve, reject) {
let gZipPromises = [];
$.each(element2, function(data2) {
gZipPromises.push(
getZip(data2).then(function(txt){ //after everything has finished this get's called
out = someFunction(txt,data2);
total.push(out);
});
);
)};
Promise.all(gZipPromises).then(function() {
resolve()
});
}).then(function () {
console.log(total)
});
)};
const elements = [["foo.zip"],["bar.zip"],["baz.zip"]];
const totalOut = getAllZips(elements)
.then(text => console.info(text))
.catch(error => console.error(error))
function someFunction(text, data) {
return `${text}\nLength: ${data.length}`;
}
async function getAllZips(elements) {
let promises = [];
for(const element of elements) {
for(const data of element) {
promises.push(getZip(data).then(text => {
return someFunction(text, data);
}));
}
}
return Promise.all(promises);
}
async function getZip(file) {
return new Promise((resolve, reject) => {
JSZipUtils.getBinaryContent(`someURL/${file}`, async (err, data) => {
try {
if (err) throw err;
const zip = await JSZip.loadAsync(data);
const name = file.replace(".zip", "");
resolve(await zip.file(name).async('text'));
} catch(error) {
reject(error);
}
});
});
}
<script>/*IGNORE*/const JSZipUtils = {getBinaryContent:(p,c)=>errs.gbc?c(new Error('gbc'),null):c(null,{foo:true})};const JSZip = {loadAsync:(d)=>errs.la?Promise.reject(new Error('la')):({file:n=>({async:a=>errs.a?Promise.reject(new Error('a')):Promise.resolve('Hello World')})})};const errs = {gbc:false,la:false,a:false};/*IGNORE*/</script>
This kind of sounds like a use case for async iterator generators, but maybe I'm just over-engineering. You have a bunch of resources that you want to iterate over and their contents are asynchronous. You want it to "look" synchronous, so you can leverage async/await:
function getZip(zipFile) {
/*
* Theres no point in simplifying this function since it looks like
* the JSZip API deals with callbacks and not Promises.
*/
return Promise.resolve(zipFile);
}
function someFn(a, b) {
return `${a}: ${b.length}`;
}
async function* zipper(elements) {
for (const element of elements) {
for (const data of element) {
const txt = await getZip(data);
yield someFn(txt, data);
}
}
}
(async() => {
const elements = [
["hello"],
["world"],
["foo"],
["bar"]
];
let total = [];
for await (const out of zipper(elements)) {
total.push(out);
}
console.log(total);
})();
I have a conditional statement in which I need to perform one of two operations, then continue after whichever operation has resolved. So my code currently looks as follows:
if (shoud_do_thing_a) { //should_do_thing_a is just a variable that determines which function to call. it is not a promise
do_thing_a()
} else {
do_thing_b()
}
// more code
The issue is that both do_thing_a and do_thing_b return promises, and I can't move on until whichever gets executed has resolved. The best way I've come up with to solve this is like this:
var more_code = function () {
// more code
}
if (shoud_do_thing_a) {
do_thing_a().then(more_code)
} else {
do_thing_b().then(more_code)
}
I don't like this structure. It's difficult to follow because you need to jump around to find where more_code is defined (imagine I have this type of control flow in several locations), rather than simply being able to continue reading.
Is there a better way to deal with this type of thing in javascript?
If you can use async/await
async function someFunc() {
var more_code = function () {
// more code
}
if (shoud_do_thing_a) {
await do_thing_a()
} else {
await do_thing_b()
}
more_code()
}
Or if you can't, use then():
var more_code = function () {
// more code
}
var do_thing;
if (shoud_do_thing_a) {
do_thing = do_thing_a()
} else {
do_thing = do_thing_b()
}
do_thing.then(more_code)
If you're stuck with raw Promises and can't use async/await (You usually should have no trouble, what with babel/typescript etc), the following is a bit more elegant than storing the promise in a variable:
function something() {
return Promise.resolve()
.then(() => {
if (should_do_thing_a) {
return do_thing_a();
}
else if (should_do_thing_b) {
return do_thing_b();
}
})
.then(some_more_code);
}
Note that when you start working with Promises, your functions should always return a Promise that other functions can work with. Leaving an asynchronous action without any way to handle it means bad things, especially when it comes to error handling.
In a more general sense, it means that when you use Promises, more of your code is "uplifted" into being executed and returned as Promises.
How I want to improve on other answers:
keep it clean and simple
no unneeded variables
return promise asap
in js we use camelCase
put it in a function and name that function to keep it readable
let then execute moreCode so it's called after the thing is done.
function doTheThing () {
if (shouldDoA) return doThingA()
else return doThingB()
}
doTheThing().then(moreCode)
Simple working example:
The scope it's defined in must be async.
const createUser = async (data) => {
if (await isUsernameTaken(username)) { return 'username-taken' }
}
The isUsernameTaken func:
const isUsernameTaken = async (username) => {
const request = await API.SomeRequest
return !request.isEmpty
}
Save the promise and add the then after the if statement:
var promise;
if (shoud_do_thing_a) {
promise = do_thing_a();
} else {
promise = do_thing_b();
}
promise.then(more_code);
var promise = shoud_do_thing_a? do_thing_a: do_thing_b
promise().then(function () {
// more code
})
Similar to other answers here, but you can self execute the async and clean up the condition a bit.
(async () => {
const should_do_thing_a = true
const do_thing_a = function() {
return new Promise(function(resolve, reject) {
resolve('a')
})
}
const do_thing_b = function() {
return new Promise(function(resolve, reject) {
resolve('b')
})
}
const result = (should_do_thing_a) ? await do_thing_a() : await do_thing_b()
console.log(result)
})()
The way I would do it would be to put the if check into another function that returns a promise. The promise gets resolved with the resolve of the other function calls in the if-else statement.
Example:
function performCheck(condition) {
var defer = $q.defer();
if (condition) {
doThingA().then(function(response) {
defer.resolve(response);
});
} else {
doThingB().then(function(response) {
defer.resolve(response)
});
}
return defer.promise;
}
performCheck(condition).then(function(response) {
//Do more code.
});
In my opinion, I would prefer this method because this function can now be used in multiple places where you have a check on the condition, reducing code duplication, and it is easier to follow.
You could reduce this down further with
function performCheck(condition) {
var defer = $q.defer();
var doThisThing = condition ? doThingA : doThingB;
doThisThing().then(function (response) {
defer.resolve(response);
});
return defer.promise;
}
performCheck(condition).then(function(response) {
//Do more code.
});
You can use async/await
async function fn() {
let p, result;
if (shoud_do_thing_a) {
p = await do_thing_a()
} else {
p = await do_thing_b()
}
if (p) {
result = more_code();
}
return result
}
more_code = miFunc() => return new Promise((resolve, reject) => ... });
Solution 1
const waitFor = should_do_thing_a ? do_thing_a() : do_thing_b();
waitFor.then(...).catch(...)
Solution 2
let waitFor = Promise.resolve();
if (do_thing_a) {
waitFor = do_thing_a();
} else {
waitFor = do_thing_b();
}
waitFor.then(...).catch(...);
I'm processing a list in a loop that's run async returning a promise and I do not want to exit processing on exception, so I aggregate them and pass them to the resolve callback in an outer finally block.
I'll like to know if this is an anti-pattern and if so, please provide a pointer on how to do correctly.
Thanks.
Example
async doSomething(list) {
let errorCount = 0
let errors = []
return new Promise(async (resolve, reject) => {
try {
list.forEach(async (item) => {
try {
actionThatThrows(item)
} catch (e) {
errorCount++
errors[errorCount] = e
}
})
} catch (e) {
errorCount++
errors[errorCount] = e
} finally {
if (errorCount > 0) {
resolve(errors)
} else {
resolve()
}
}
})
}
Yes, this code employs several antipatterns:
Never pass an async function to a Promise constructor
Never use the Promise constructor when you already have promises around
forEach does not work with async functions
I do not want to exit processing on exception but aggregate them
You might want to have a look at Wait until all ES6 promises complete, even rejected promises for that.
But you can do it without those as well, assuming you want sequential iteration:
async function doSomething(list) {
const errors = [];
for (let item of list) {
try {
await actionThatThrows(item);
} catch (e) {
errors.push(e);
}
}
if (errors.length)
return errors;
else
return …;
}
The errors are the result of your asynchronous computation so it globally looks legit.
Assuming that actionThatThrows returns a promise (it's unclear in your question and code), it looks like it could be written like this:
function doSomething(list) {
let errors = []
return Promise.all(list.map(
item => actionThatThrows(item).catch(e => {
errors.push(e);
})
)).then(()=>{
return errors.length ? errors : undefined;
});
}
1) async doSomething is not invoking an await, so remove async
2) async in list.forEach is not invoking an await, so remove async
3) First catch will catch all. Second catch will never be hit, so remove second catch and finally
Code can be simplified to:
doSomething(list) {
let errorCount = 0,
errors = [];
for (let item of list) {
try {
actionThatThrows(item); //I suppose this is not returning a promise
} catch (e) {
errorCount += 1;
errors[errorCount] = e;
}
}
if (errorCount > 0) {
return errors; //return Promise.reject(errors);
} else {
//return Promise.resolve();
}
}