I have an async function I have to call it under the map function,
class DataArticle {
id:number,
title:string,
...
user:User // User Entity
}
I want to get articles then assign to each article the author of it:
var dtresult = this.articleRepo.findAll(); // get all articles
const result:DataArticle[] = dtresult.map(async (a:DataArticle) => {
let user = await this.UserRepo.getUser(a.id)
a.user = user ; // assign user to the article after getting the user
return a ;
})
I tried implemeting async function with this way and it doesn't work
You don't need to use another library for this task.
It can be achieved by Promise.All (or Promise.allSettled)
async function getUser(id){return id;}
let data = [...Array(10).keys()]
async function the_caller_function(){
const result = await Promise.all(data.map(async a=> {
let user = await getUser(a)
return {user:a}
}))
console.log(result)
}
the_caller_function();
Related
I'm fetching the stylesheet and replacing all CSS variables with the actual hex value it corresponds to when the user changes the color as desired.
I created an event handler so that when the user clicks the download button, all of the colors he/she selected, would be saved in the stylesheet at that moment, but it doesn't seem to work. I know it's an issue with my understanding of promises as a whole and async await
What I did.
const fetchStyleSheet = async () => {
const res = await fetch("./themes/prism.css");
const orig_css = await res.text();
let updated_css = orig_css;
const regexp = /(?:var\(--)[a-zA-z\-]*(?:\))/g;
let cssVars = orig_css.matchAll(regexp);
cssVars = Array.from(cssVars).flat();
console.log(cssVars)
for await (const variable of cssVars) {
const trimmedVar = variable.slice(6, -1)
const styles = getComputedStyle(document.documentElement)
const value = String(styles.getPropertyValue(`--${trimmedVar}`)).trim()
updated_css = updated_css.replace(variable, value);
}
console.log(updated_css)
return updated_css
}
const main = async () => {
const downloadBtn = document.getElementById('download-btn')
downloadBtn.addEventListener('click', () => {
const updated_css = fetchStyleSheet()
downloadBtn.setAttribute('href', 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(updated_css))
downloadBtn.setAttribute('download', 'prism-theme.css')
})
}
main()
I can't await the updated_css because it falls into the callback of the click event, which is a new function.
Then I did the following thinking it would work since it was top level.
const downloadBtn = document.getElementById('download-btn')
downloadBtn.addEventListener('click', async () => {
const updated_css = await fetchStyleSheet()
downloadBtn.setAttribute('href', 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(updated_css))
downloadBtn.setAttribute('download', 'prism-theme.css')
})
That gave me the following error TypeError: NetworkError when attempting to fetch resource.
I understand that calling fetchStyleSheet() only returns a promise object at first and to get the value (which is updated_css), I need to follow it with .then() or await it.
The await is the correct approach to deal with the fetchStyleSheet() call returning a promise, your problem is that the click on the link tries to follow the href attribute immediately - before you set it to that data url. What you would need to do instead is prevent the default action, asynchronously do your stuff, and then re-trigger the click when you're done. Also don't forget to deal with possible exceptions:
const downloadBtn = document.getElementById('download-btn')
downloadBtn.addEventListener('click', async (event) => {
if (!e.isTrusted) return // do nothing on the second run
try {
event.preventDefault()
const updated_css = await fetchStyleSheet()
downloadBtn.setAttribute('href', 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(updated_css))
downloadBtn.setAttribute('download', 'prism-theme.css')
downloadBtn.click() // simulate a new click
} catch(err) {
console.error(err) // or alert it, or put the message on the page
}
})
I record favorite offers that a user 'hearts' in my app . These records include the owner and offer IDs. I want to collect the top 25 favorited Offers for a particular user. All firestore commands are asynchronous and I need to collect all the offer objects before I render the page.
Its my first time using async / await and what started as one has quickly grown into nested async / awaits. There must be a simpler way to collect the IDs from the fav objects and then lookup the Offers with those IDs?
async getItems() {
const collectfavs = async () => {
favsRef = firestore.collection('favs').where('owner','==',getUserID()).orderBy('created', 'desc').limit(25);
let allFavsSnapshot = await favsRef.get();
allFavsSnapshot.forEach(doc => {
let data = doc.data();
favsList.push(data.offer);
});
console.log('favs:',favsList);
}
const collectoffers = async () => {
favsList.forEach(async (fav) => {
let doc = await firestore.collection('offers').doc(fav).get()
console.log('doc:', doc);
let data = doc.data();
data.id = doc.id;
offerList.push(data);
});
console.log('offers:', offerList);
}
await collectfavs();
await collectoffers();
}
I'm not sure why you're defining two local functions just to call them each once. That seems like more code than necessary to get the job done. Other than that, what you're doing doesn't seem very complex to me. But if you want to reduce the lines of code:
async getItems() {
favsRef = firestore.collection('favs').where('owner','==',getUserID()).orderBy('created', 'desc').limit(25);
let allFavsSnapshot = await favsRef.get();
allFavsSnapshot.forEach(doc => {
let data = doc.data();
favsList.push(data.offer);
});
console.log('favs:',favsList);
favsList.forEach(async (fav) => {
let doc = await firestore.collection('offers').doc(fav).get()
console.log('doc:', doc);
let data = doc.data();
data.id = doc.id;
offerList.push(data);
});
console.log('offers:', offerList);
}
Bear in mind that I have no idea where you defined favsList and offerList, so I'm just blindly using it the same way you showed.
As I understand from your comment to Doug Stevenson above you want to ensure that you lists will be filled before using them. In order to do that you could you Promises. What Promises do is give us a way to deal with asynchrony in the code in a sequential manner.
All you need to do is create a promise that will ensure that you first fill the Lists you want and then you use them.
Let me know if this helps.
I ended up needing to create my own asyncForEach routine to accommodate the async calls to firebase inside the loop. Thanks to an article by Sebastien Chopin asyncForEach
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
and then in my getItems:
await asyncForEach(favsList, async (fav) => {
let doc = await firestore.collection('offers').doc(fav).get()
let data = doc.data();
data.id = doc.id;
offerList.push(data);
});
I made a transaction function that simplifies this action for me like this (it working):
export async function transaction(queriesRaw) {
let allResults = []
const client = await pool.connect()
try {
await client.query('BEGIN')
var queries = queriesRaw.map(q => {
return client.query(q[0], q[1])
})
for await (const oneResult of queries) {
allResults.push(oneResult)
}
await client.query('COMMIT')
} catch (err) {
await client.query('ROLLBACK')
} finally {
client.release()
return allResults
}
}
And do transactions like this:
let results = await transaction([
['UPDATE readers SET cookies=5 WHERE id=$1;', [1]],
['INSERT INTO rewards (id) VALUES ($1);', [3]]
])
Transaction should do queries one at a time in array index sequence (so rollback to previos values will work correctly) and return in the same order (sometimes i need return values from some queries)
As i understand it starts already in map map function. In for await i just wait for results of it and second query may complete faster that previos.
So how can i fix this?
P.S. Maybe something like new Promise() instead map is the rigth way?
Change this:
var queries = queriesRaw.map(q => {
return client.query(q[0], q[1])
})
for await (const oneResult of queries) {
allResults.push(oneResult)
}
To:
for(const q of rawQueries) {
let result = await client.query(q[0], q[1]);
allResults.push(result);
});
If i got you correctly, Just use a for loop with proper await, instead of a callback style loop.
So you can wait with the function to return unil everything is chronologically executed, With some thinking, you can easily add aa revoke() function or something..
...
export async function transaction(queriesRaw) {
let allResults = []
const client = await pool.connect()
try {
await client.query('BEGIN')
for(var i = 0; i < queriesRaw.length;i++) {
var res = await client.query(queriesRaw[i][0], queriesRaw[i][1])
allResults.push(res)
}
await client.query('COMMIT')
} catch (err) {
await client.query('ROLLBACK')
// do you maybe wanna errors to results to?
// allResults.push(err)
} finally {
client.release()
return allResults
}
}
Info,
Have a look at for example async module, or something similar. So you will not have to think about things like this.
I'm trying to create a function that builds a queue from an array of objects and then processes each object by calling a number of functions.
The processing functions are asynchronous functions which, prior to needing to queue, I'd implemented using the async/await pattern. I think this is necessary as each relies on the output of the previous and I don't want to have a tonne of nested promise.then's
i.e. previously I had:
await Promise.all(messages.map(async(message) => {
let activity = await activityController.getActivity(message.activityId);
let url = await SMSController.getUrl(message.Token);
let smsSendResult = await SMSController.sendSMS(messageString, activity.mobileNo);
// etc...
}
Now what I want to be able to do is:
let queue = async.queue((message, done) => {
let activity = await activityController.getActivity(message.activityId);
let smsSendResult = await SMSController.sendSMS(messageString, activity.mobileNo);
// etc...
}
messages.forEach((message) => {
queue.push(message);
})
I have the problem though that this results in
SyntaxError: await is only valid in async function
And I can't seem to quite get my head around how to get past this.
You're looking for async.series, not async.queue:
series(tasks, callbackopt)
Run the functions in the tasks collection in series, each one running once the previous function has completed.
So just following the docs:
const messageCallbacks = messages.map(function(msg) {
return async function(callback) {callback(await handleMessage(msg));
});
async.series(messageCallbacks,
// optional callback
function(err, results) {
// results is now equal to whatever handleMessage resolves to
});
Without async:
async function asyncMessageQueue(messages) {
const results = [];
for(var i=0,l=messages.length; i<l; ++i) {
results.push(await handleMessage(messages[i]));
}
return results;
}
async function handleMessage(message) {
let activity = await activityController.getActivity(message.activityId);
let url = await SMSController.getUrl(message.Token);
let smsSendResult = await SMSController.sendSMS(messageString, activity.mobileNo);
// rest of the code
};
This also allows you to provide the next message with any previous results:, just change await handleMessage(messages[i]) to await handleMessage(messages[i], results) and then:
async function handleMessage(message, prevResults) {
// rest of the code
};
I found the asyncify function in the async module which allows me to do this:
var queue = async.queue(async.asyncify(async (message, done) => {
let url = await SMSController.getUrl(message.token);
// etc...
}
I am trying to scrap wikipedia page to fetch list of airlines by first scrapping first page and then going to each individual page of airline to get the website url. I have divided the code in two functions. One to scrap main page and get a new url, and second function to scrap another page from the created url to get the website name from that page. I have used request-promise module for getting the html and then cheerio to parse the data.
export async function getAirlinesWebsites(req,res) {
let response = await request(options_mainpage);
console.log(`Data`);
let $ = cheerio.load(response);
console.log('Response got');
$('tr').each((i,e)=>{
let children = '';
console.log('inside function ', i);
if($(e).children('td').children('a').attr('class') !== 'new') {
children = $(e).children('td').children('a').attr('href');
let wiki_url = 'https://en.wikipedia.org' + children;
console.log(`wiki_url = ${wiki_url}`);
let airline_url = getAirlineUrl(wiki_url);
console.log(`airline_url = ${airline_url}`);
}
})
And then the getAirlineUrl() function will parse another page based on the provided url.
async function getAirlineUrl(url){
const wiki_child_options = {
url : url,
headers : headers
}
let child_response = await request(wiki_child_options);
let $ = cheerio.load(child_response);
let answer = $('.infobox.vcard').children('tbody').children('tr').children('td').children('span.url').text();
return answer;
})
However when I console log the answer variable in the parent function, I get a [object Promise] value instead of a String. How do I resolve this issue?
Async function return promise.In case of that,you need to use then to get resolved response or use await.
This should work if other part of your code is ok.
export async function getAirlinesWebsites(req, res) {
let response = await request(options_mainpage);
console.log(`Data`);
let $ = cheerio.load(response);
console.log("Response got");
$("tr").each(async (i, e) => {
let children = "";
console.log("inside function ", i);
if ($(e).children("td").children("a").attr("class") !== "new") {
children = $(e).children("td").children("a").attr("href");
let wiki_url = "https://en.wikipedia.org" + children;
console.log(`wiki_url = ${wiki_url}`);
let airline_url = await getAirlineUrl(wiki_url);
console.log(`airline_url = ${airline_url}`);
}
});
}
Since your getAirlineUrl function returns a promise, you need to await that promise. You can't have await nested inside of the .each callback because the callback is not an async function, and if it was it wouldn't work still. The best fix is the avoid using .each and just use a loop.
export async function getAirlinesWebsites(req,res) {
let response = await request(options_mainpage);
console.log(`Data`);
let $ = cheerio.load(response);
console.log('Response got');
for (const [i, e] of Array.from($('tr')).entries()) {
let children = '';
console.log('inside function ', i);
if($(e).children('td').children('a').attr('class') !== 'new') {
children = $(e).children('td').children('a').attr('href');
let wiki_url = 'https://en.wikipedia.org' + children;
console.log(`wiki_url = ${wiki_url}`);
let airline_url = await getAirlineUrl(wiki_url);
console.log(`airline_url = ${airline_url}`);
}
}
}