I have gone through several questions and posts regarding how to accomplish this but none have worked. I have tried an array (no pun intended) of possible solutions involving
Promise.*, thenable, await and none have worked.
I have an array of payments that I extract from a Prisma db call, the object looks like this:
[{
"id": 1,
"amount": 100,
"payment_data": [{
"credits": 2,
"subscription_id": 534
}, {
"credits": 3,
"subscription_id": 469
}],
"customerId": 14
}]
I am trying to run another database query using the subscription_id under each payment_data for which I built a simple function:
const get_subscription = async function (subscription_id) {
return await db.subscriptions.findUnique({
where: {
id: Number(subscription_id)
}
})
}
And then I execute it inside a .map like this:
const mapped_payments = payments.map((payment) => ({
...payment,
payment_data: payment.payment_data.map(function (data) {
data.subscription = get_subscription(data.subscription_id).then((resp => resp))
return data
})
}))
The problem is no matter what I have tried this never resolves.
If I console log mapped_payments the subscription object shows as Promise {<pending>} and entirely empty when I return the response in express.
I am sure this is a simple solution but I have moved all possible solutions (Promise.*, thenable, await) into the function, into the .map and into the res.send but none work.
Any help is appreciated!
You need to use async map callback to await the get_subscription function, Also it would be better to use Promise.all to handle all mapped promises.
const mapped_payments = await Promise.all(payments.map(async (payment) => ({
...payment,
payment_data: await Promise.all(payment.payment_data.map(async function (data) {
data.subscription = await get_subscription(data.subscription_id)
return data
}))
})))
Related
I want to get the comments from a YouTube video and do some further work with them. I started with the implementation example from the docs for "CommentThreads: list" and continued from there on.
Using a main function I want to call the execute function and use the response it should return. It should look kinda like the following:
function execute() {
return gapi.client.youtube.commentThreads.list({
"part": [
"snippet"
],
"fields":[
"items/snippet/topLevelComment/snippet(authorDisplayName,authorProfileImageUrl,publishedAt,textDisplay)"
],
"videoId": "XXXXX"
}).then(function(response) {
console.log(response.result);
},
function(err) {
console.error("Execute error", err);
});
}
function main(){
let response = execute();
console.log(response);
}
And the output in the console looks like this:
{
"Ca": 2,
"yb": null,
"Yj": null,
"Sn": null,
"KB": false,
"xw": false
}
Another way I tried to solve this is to not return gapi.client.youtube.commentThreads.list but the response in the successful promise function like so:
function execute() {
gapi.client.youtube.commentThreads.list({
"part": [
"snippet"
],
"fields":[
"items/snippet/topLevelComment/snippet(authorDisplayName,authorProfileImageUrl,publishedAt,textDisplay)"
],
"videoId": "XXXXX"
}).then(function(response) {
console.log(response.result);
return reponse.result;
},
function(err) {
console.error("Execute error", err);
});
}
But here I only get an undefined.
I am just learning JavaScript and the use of the API.
EDIT:
I should also add that the console.log(response.result); in the execute function does print the information I want. But as soon as I return it and want to use it in the main() function it changes.
The problem seems to that when using the gapi that it returns a promise that I want to return. Promised are not evaluated immediately which means that it continues to execute the following functions, returning a value that has no response yet.
I solved the issue by using the async/await functionality to work with promises, which seemed to be more understandable and elegant in my eyes. I also changed some variables to constants as they did not need to be non-constants. My result looks like the following:
await function execute() {
let response = await gapi.client.youtube.commentThreads.list({
"part": [
"snippet"
],
"fields":[
"items/snippet/topLevelComment/snippet(authorDisplayName,authorProfileImageUrl,publishedAt,textDisplay)"
],
"videoId": "XXXXX"
});
return response.result;
}
async function main(){
let response = await execute();
console.log(response);
}
The reason why we also have to use async and await in the main() function is that by definition async functions always return promises.
To learn more about async/await I found this article whose explanation I liked: https://javascript.info/async-await
They also have an article about promises: https://javascript.info/promise-basics
Hey guys hope u are doing well :) I have this situation that I dont know how to solve, so Im trying to fill nuxt.js sitemap of my dynamic websites, so when I had only one call all worked fine and I got what I needed in sitemap.xml but when I add other one I guess I need to concat results, but I dont know how...
sitemap: {
routes: async () => {
let { data } = await axios.get("http://localhost:1337/articles");
return data.map(v => `${v.slug}`)
}
},
I need to add same stuff just with diferent api "http://localhost:1337/faq/slug"
And with one result of both api calls, I need to fill sitempa.xml :)
Using the spread syntax, you can do this
sitemap: {
routes: async () => {
let { data: articlesData } = await axios.get("http://localhost:1337/articles");
const articlesArray = articlesData.map(v => `${v.slug}`)
let { data: slugData } = await axios.get("http://localhost:1337/faq/slug");
const slugsArray = slugData.map(v => `${v.slug}`)
return [...articlesArray, ...slugsArray]
}
},
This should work fine if you want to join 2 arrays. Otherwise, give us an example of what is the end structure we need here.
PS: articlesData and slugData are renamed on the fly here to avoid redefining the same variable and keep things clean.
I had this problem in my project.
To solve this problem I did make one request for my api. In Api I created a method in which I processed all requests.
sitemap: {
path: '/sitemap.xml',
cacheTime: 1000 * 60 * 1440,
hostname: process.env.SITEURL,
routes: async () => {
const {data} = await axios.get(`${process.env.BACKADDR}/getSiteMapRoutes`);
return data.routers
}
},
I want to define the response structure of my requests in the simplest way, and the first thing that comes in my mind to do this is a middleware.
My endpoints are returning the response content correctly:
{{base_url}}/users returns a list of users:
{
[
{
"id": 44,
"name": "some name"
[...]
}
]
}
What I want to do (in all requests) is to add the fields status and data (or any other I'd like to add), like this:
{
"status": 200,
"data": [
{
"id": 44,
"name": "some name"
[...]
}
]
}
I've created a middleware that waits for the resolution but I'm not able to get the content nor add some property to it.
[...]
async handle ({request, response}, next) {
await next()
const content = response._lazyBody.content
content.status = response.response.statusCode
}
[...]
I know this will not work but I want something similar to this. I've looked in Adonis docs and forum, but no answers fit to my needs.
Any help will be welcome
You can extend Response By extending the core. The simplest way is to create a file inside start folder and name it hooks.js and copy and paste the content below inside it:
const { hooks } = use('#adonisjs/ignitor')
const Response = use('Adonis/Src/Response')
hooks.after.providersBooted(() => {
Response.macro('customJson', function (status, data) {
this.status(status).json({
status,
data
})
})
})
this piece of code extends the Response module and add customJson method to it which takes two arguments, status and data, and send them back to the client.
And here you can see how to use it:
Route.get('/users', async ({ response }) => {
let status = ''// whatever you want
let data = ''// whatever you want
return response.customJson(status, data)
})
I don't know why but I have some problems with my Dashboard.
So basically I want to create some fancy Donut Charts.
For that I've prepared a dataset-Array where I put my numbers in. All that works.
But when I get my data from the database I want to change the array, to update the Chart.
This is where I have problems.
So my data() looks like this:
data() {
return {
disturbances_category_0: [],
disturbances_category_1: [],
disturbances_category_2: [],
disturbances_category_3: [],
datasets: [
{
data: [20, 20, 10, 50], //HERE I HAVE TO CHANGE THE NUMBERS <-------------
backgroundColor: ["#A40000", "#580000", "#EC4A3B", "#179C7D"],
hoverBackgroundColor: ["#ff1a1a", "#b30000", "#f4948b", "#66bfac"]
}
],
labels: ["Banana", "Apple", "Strawberry", "Cherry"],
option: {}
};
},
And then there is my created()-Block, where I use Axios + Sequelize and Feathers to get my data:
created() {
axios.get('http://localhost:3030/disruptions/', {
params: {
DisruptionCategory: 0
}
})
.then((response) => {
this.disturbances_category_0 = response.data.data; //HERE IS THE COMPLETE ARRAY
this.datasets[0].data[0] = this.disturbances_category_0.length; //HERE I WANT TO SET THE LENGTH
})
.catch((error) => {
console.log(error.data);
});
//imagine that for the other fruits as well...
console.log(this.datasets[0].data[0]);
}
If I test this script I always get "20" as printout.
I don't know why it doesn't change the datasets.data-Array ... I also tried out to use Array.push but... nothing happened..
I'm sure I forgot something obvious...
It is because the console log likely happened long before your then block executed. Its initial value is an array of four integers before you overwrite it with a length. Try making the created function async and await the axios promise chain to resolve.
async function created() {
await axios.get('http://localhost:3030/disruptions/', { // await the resolve
params: {
DisruptionCategory: 0
}
})
.then((response) => {
this.disturbances_category_0 = response.data.data; //HERE IS THE COMPLETE ARRAY
this.datasets[0].data[0] = this.disturbances_category_0.length; //HERE I WANT TO SET THE LENGTH
})
.catch((error) => {
console.log(error.data);
});
//imagine that for the other fruits as well...
console.log(this.datasets[0].data[0]); // now this should be updated
}
console.log(this.datasets[0].data[0]);
The above will run before the response to your request has been handled since it is asynchronous. Your code will just keep executing while the .then() part will execute on another thread once you get a response from the server.
I need to call an API recursively using request promise after getting result from API need to write in an excel file , API sample response given below
{
"totalRecords": 9524,
"size": 20,
"currentPage": 1,
"totalPages": 477,
"result": [{
"name": "john doe",
"dob": "1999-11-11"
},
{
"name": "john1 doe1",
"dob": "1989-12-12"
}
]
}
Now I want to call this API n times, here n is equal to totalPages, after calling each API I want to write response result to the excel files.
First write page 1 response result to excel then append page 2 response result to excel file and so on..
I have written some sample code given below
function callAPI(pageNo) {
var options = {
url: "http://example.com/getData?pageNo="+pageNo,
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
json: true
}
return request(options)
}
callAPI(1).then(function (res) {
// Write res.result to excel file
}).catch(function (err) {
// Handle error here
})
But facing problem calling recursively API and maintaining sequentially like write page 1 result first to excel file then page 2 result append to excel and so on..
Any code sample how to achieve in nodejs
You want to do something like this:
function getAllPages() {
function getNextPage(pageNo) {
return callAPI(pageNo).then(response => {
let needNextPage = true;
if (pageNo === 1) {
// write to file
} else {
// append to file
}
if (needNextPage) {
return getNextPage(pageNo+1);
} else {
return undefined;
}
});
}
return getNextPage(1);
}
Obviously change that 'needNextPage' to false to stop the recursion when you're done
So you want to do 477 requests in sequence? How long do you wanna wait for this to finish? Even in paralell, this would be still too long for me.
Best: write an API that can return you a batch of pages at once. Reducing the number of requests to the backend. Maybe something like http://example.com/getData?pages=1-100 and let it return an Array; maybe like
[
{
"totalRecords": 9524,
"currentPage": 1,
"totalPages": 477,
"result": [...]
},
{
"totalRecords": 9524,
"currentPage": 2,
"totalPages": 477,
"result": [...]
},
...
]
or more compact
{
"totalRecords": 9524,
"totalPages": 477,
"pages": [
{
"currentPage": 1,
"result": [...]
},
{
"currentPage": 2,
"result": [...]
},
...
]
}
Sidenote: writing the size of the results array into the json is unnecessary. This value can easily be determined from data.result.length
But back to your question
Imo. all you want to run in sequence is adding the pages to the sheet. The requests can be done in paralell. That already saves you a lot of overall runtime for the whole task.
callApi(1).then(firstPage => {
let {currentPage, totalPages} = firstPage;
//`previous` ensures that the Promises resolve in sequence,
//even if some later request finish sooner that earlier ones.
let previous = Promise.resolve(firstPage).then(writePageToExcel);
while(++currentPage <= totalPages){
//make the next request in paralell
let p = callApi(currentPage);
//execute `writePageToExcel` in sequence
//as soon as all previous ones have finished
previous = previous.then(() => p.then(writePageToExcel));
}
return previous;
})
.then(() => console.log("work done"));
or you wait for all pages to be loaded, before you write them to excel
callApi(1).then(firstPage => {
let {currentPage, totalPages} = firstPage;
let promises = [firstPage];
while(++currentPage < totalPages)
promises.push(callApi(currentPage));
//wait for all requests to finish
return Promise.all(promises);
})
//write all pages to excel
.then(writePagesToExcel)
.then(() => console.log("work done"));
or you could batch the requests
callApi(1).then(firstPage => {
const batchSize = 16;
let {currentPage, totalPages} = firstPage;
return Promise.resolve([ firstPage ])
.then(writePagesToExcel)
.then(function nextBatch(){
if(currentPage > totalPages) return;
//load a batch of pages in paralell
let batch = [];
for(let i=0; i<batchSize && ++currentPage <= totalPages; ++i){
batch[i] = callApi(currentPage);
}
//when the batch is done ...
return Promise.all(batch)
//... write it to the excel sheet ...
.then(writePagesToExcel)
//... and process the next batch
.then(nextBatch);
});
})
.then(() => console.log("work done"));
But don't forget to add the error handling. Since I'm not sure how you'd want to handle errors with the approaches I've posted, I didn't include the error-handling here.
Edit:
can u pls modify batch requests, getting some error, where you are assigning toalPages it's not right why the totalPages should equal to firstPage
let {currentPage, totalPages} = firstPage;
//is just a shorthand for
let currentPage = firstPage.currentPage, totalPages = firstPage.totalPages;
//what JS version are you targeting?
This first request, callApi(1).then(firstPage => ...) is primarily to determine currentIndex and totalLength, as you provide these properties in the returned JSON. Now that I know these two, I can initiate as many requests in paralell, as I'd want to. And I don't have to wait for any one of them to finish to determine at what index I am, and wether there are more pages to load.
and why you are writing return Promise.resolve([ firstPage ])
To save me some trouble and checking, as I don't know anything about how you'd implement writePagesToExcel.
I return Promise.resolve(...) so I can do .then(writePagesToExcel). This solves me two problems:
I don't have to care wether writePagesToExcel returns sync or a promise and I can always follow up with another .then(...)
I don't need to care wether writePagesToExcel may throw. In case of any Error, it all ends up in the Promise chain, and can be taken care of there.
So ultimately I safe myself a few checks, by simply wrapping firstPage back up in a Promise and continue with .then(...). Considering the amounts of data you're processing here, imo. this ain't too much of an overhead to get rid of some potential pitfalls.
why you are passing array like in resolve
To stay consistent in each example. In this example, I named the function that processes the data writePagesToExcel (plural) wich should indicate that it deals with multiple pages (an array of them); I thought that this would be clear in that context.
Since I still need this seperate call at the beginning to get firstPage, and I didn't want to complicate the logic in nextBatch just to concat this first page with the first batch, I treat [firstPage] as a seperate "batch", write it to excel and continue with nextBatch
function callAPI(pageNo) {
var options = {
url: "http://example.com/getData?pageNo="+pageNo,
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
json: true
}
return request(options)
}
function writeToExcel(res){console.log(res)} //returns promise.
callAPI(1).then(function (res) {
if(res){
writeToExcel(res).then(() => {
var emptyPromise = new Promise(res => setTimeout(res, 0));
while(res && res.currentPage < res.totalPages){
emptyPromise = emptyPromise.then(() => {
return callAPI(res.currentPage).then(function (res){
if(res){
writeToExcel(res)
}
});
}
}
return emptyPromise;
});
}
}).catch(function (err) {
// Handle error here
})