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.
Related
In the following exported function which is from a Nextjs app as an API page, the domainnames array is returning nothing in the 200 response.
However, if I do not use the GetDomainStatus() function and just push items from response.data.results into domainnames, then the JSON response is filled.
export default function GetSuggestions(req, res){
const keyword = req.query.q;
const tlds = '.com,.net,.io,.org,.co,.xyz,.app,.us,.blog,.shop,.land,.video,.review,.host,.dev';
let queryPath = `${suggestionsURL}?include-registered=false&tlds=${tlds}&include-suggestion-type=true&sensitive-content-filter=true&use-numbers=true&max-length=20&lang=eng&max-results=100&name=${keyword}&use-idns=false`
let domainnames = [];
axios.get(queryPath).then(response => {
response.data.results.forEach(item => {
GetDomainStatus(item.name).then(a => {
domainnames.push({
name: item.name,
avail: a
})
})
})
res.status(200).json(domainnames);
});
}
is this a scope issue where I actually cannot access domainnames array from within the promise?
This solution worked. Not sure if its the best, but uses the promise.all solution.
const domainnames = [];
const promises = [];
axios.get(queryPath).then(response => {
response.data.results.forEach(item => {
let newPromise = GetDomainStatus(item.name).then(a => {
domainnames.push({
name: item.name,
avail: a
})
});
promises.push(newPromise);
})
Promise.all(promises).then(r => res.status(200).json(domainnames));
});
I have followed the instructions here to use a Google Sheet as a JSON endpoint. I am then using that data in Eleventy. This code is currently working:
module.exports = async function() {
let url = `https://docs.google.com/spreadsheets/d/${process.env.GOOGLE_SHEETS_ID}/gviz/tq?tqx=out:json`;
console.log("Fetching from Google Sheets...");
return await fetch(url)
.then(res => res.text()) // node-fetch option to transform to json
.then(text => {
let json = JSON.parse(text.substr(47).slice(0, -2));
return {
items: json.table.rows
};
});
}
...however, this is making for slow build times, so I am trying to tie this in with the eleventy-cache-assets plugin as described in the 11ty docs.
Here is what I've tried:
module.exports = async function() {
let url = `https://docs.google.com/spreadsheets/d/${process.env.GOOGLE_SHEETS_ID}/gviz/tq?tqx=out:json`;
var text = await Cache(url, {
duration: "1s",
type: "text"
})
.then(text => {
var json = JSON.parse(text.substr(47).slice(0, -2));
console.log(json);
return {
items: json.table.rows
};
});
};
In the console, it does return the JSON, but then when I try to make a collection out of the data in .eleventy.js like this:
eleventyConfig.addCollection("myGSheets", (collection) => {
return collection.getAll()[0].data.myGSheets.items;
});
I get an error: Cannot read property 'items' of undefined
I'm not sure what is happening in between the JSON data appearing in the console, and then being undefined.
I am guessing maybe I need to do the string manipulation of the response before calling Cache, perhaps? I'm just not sure how to put it all together...
It looks like you're not actually returning anything from your data function, since your return statement is inside the callback function, not the top-level data function.
module.exports = async function() { // <= top level data function
let url = `https://docs.google.com/spreadsheets/d/${process.env.GOOGLE_SHEETS_ID}/gviz/tq?tqx=out:json`;
var text = await Cache(url, {
duration: "1s",
type: "text"
})
.then(text => { // <= inner callback function
var json = JSON.parse(text.substr(47).slice(0, -2));
console.log(json);
return { // <= This return statement returns from the inner callback
items: json.table.rows
};
});
// <= This function doesn't return anything!
};
Since you're using async/await, there isn't a need for using Promise.then. You can just await the promises, which prevents needing all the callbacks.
Try:
module.exports = async function() {
let url = `https://docs.google.com/spreadsheets/d/${process.env.GOOGLE_SHEETS_ID}/gviz/tq?tqx=out:json`;
var text = await Cache(url, {
duration: "1s",
type: "text"
})
var json = JSON.parse(text.substr(47).slice(0, -2));
console.log(json);
return {
items: json.table.rows
};
};
I'm working with a Grafana plugin API and I'm trying to create a function that returns an object literal var literals that will contain new data that I fetched from the server.
The result of this function return literals; will go, as a parameter, in to another Grafana API function later.
I'm very confused by how promises work and I don't really know how to tackle this problem.
Right now in this code I'm having a problem with a console error
TypeError: data.dbs is undefined'
at the line data.dbs.forEach(element => {
I thought that after calling the .then() function the data should already be returned and yet it's still unresolved?
I would also like to know how exactly can I create this whole getDropdown() function so that it will be able to send the resolved data forward?
export function getDropdown() {
var literals = {
path: 'displayMode',
name: 'Cell display mode',
description: 'Color text, background, show as gauge, etc',
settings: {
options: [{
value: 1,
label: 'Placeholder'
}],
},
};
var data = {
dbs: [{
id: 1,
database: 'placeholder',
}, ],
};
fetch('/api/datasources')
.then(function(res) {
return res.json();
})
.then(json => {
data = json;
var counter = 0;
data.dbs.forEach(element => {
literals.settings.options[counter].label = element.database;
literals.settings.options[counter].value = element.id;
counter++;
});
});
return literals;
}
I was able to solve the problem with this code
fetch('/api/datasources')
.then(data => {
return data.json();
})
.then(data => {
data.forEach((element: { database: string; id: number }) => {
literals.settings.options[counter].label = element.database;
literals.settings.options[counter].value = element.id;
counter++;
});
});
I think there was something wrong with data.dbs but I'm still not sure what.
Insted of using then() functions, try using async/await where you can asyncronously execute functions & you can also wait till getting a promise.
In the above code you can write an async function & await till you get response from
fetch('/api/datasources')
// Do
async function getData() {
var data = await fetch('/api/datasources')
var jsonData = JSON.parse(data)
jsonData.dbs.forEach(element => {
literals.settings.options[counter].label = element.database;
literals.settings.options[counter].value = element.id;
counter++;
});
}
// Instead of
fetch('/api/datasources')
.then(function(res) {
return res.json();
})
.then(json => {
data = json;
var counter = 0;
data.dbs.forEach(element => {
literals.settings.options[counter].label = element.database;
literals.settings.options[counter].value = element.id;
counter++;
});
});
I'm new to Node/asynchronous coding and I realize this is a basic question that speaks to some fundamentals I'm missing, but for the life of me I just can't understand how this is supposed to work. I'm seeding a database using knex, by reading in data from a CSV and iterating through the rows in a for loop. Within that loop, I need to query another table in the database and return an associated value as part of the new row to be created.
I understand that knex returns a pending Promise, so it doesn't have access to the returned value yet. But I can't seem to get the structure right with async/await or Promise.all; I've tried it a bunch of ways based on other answers I've seen, but none seem to actually explain what's happening well enough that I can apply it successfully to my case. Any help hugely appreciated.
My seed file currently looks like this:
exports.seed = function(knex) {
const fs = require('fs');
function createImage(row, event_id) {
return {
name: row[1],
event_id: event_id
}
};
function get_event_id(loc) {
knex('events')
.where({location: loc})
.first()
.then(result => {console.log(result)}); // <-- this never executes
};
let images = [];
const file = fs.readFileSync('./data.csv');
const lines = file.toString().replace(/[\r]/g, '').split('\n');
for (i=1; i<lines.length; i++) {
var row = lines[i].split(',');
var event_id = (async function () { return await get_event_id(row[0]) }) ();
images.push(createImage(row, event_id));
};
console.log(images);
// Inserts seed entries
// return knex('table_name').insert(images);
};
Output:
[ { name: '003.jpg', event_id: undefined } ]
My data.csv is structured like:
Location,Name
Amsterdam,003.jpg,
...
You can change your for loop into an Array.map and use Promise.all on returned promises.
Also, seed will return a promise so invoke it properly
exports.seed = async function (knex) {
const fs = require('fs');
function createImage(row, event_id) {
return {
name: row[1],
event_id: event_id
}
};
function get_event_id(loc) {
return knex('events')
.where({ location: loc })
.first()
.then(result => { console.log(result); return result }); // <-- this never executes
};
const file = fs.readFileSync('./data.csv');
const lines = file.toString().replace(/[\r]/g, '').split('\n');
let promises = lines.map(async (line) => {
let [row] = line.split(',');
let event_id = await get_event_id(row)
return createImage(row, event_id)
});
let images = await Promise.all(promises);
console.log(images);
// Inserts seed entries
// return knex('table_name').insert(images);
};
A bit of an ambiguous title, but I'll explain...
I am making a fetch call to 4 different URLs from The Movie Database.
Once these fetch calls retrieve the data, it will then setState and update my initial state. However, I don't want my page to load until all of the data is retrieved, so I am using Promise.all (or attempting to).
My code so far...
state = {
movies: {
trending: {},
topRated: {},
nowPlaying: {},
upcoming: {},
},
};
const allMovieURLs = {
trending: `https://api.themoviedb.org/3/trending/all/day?api_key=${API_KEY}`,
topRated: `https://api.themoviedb.org/3/movie/top_rated?api_key=${API_KEY}&language=en-US&page=1`,
nowPlaying: `https://api.themoviedb.org/3/movie/now_playing?api_key=${API_KEY}&language=en-US&page=1`,
upcoming: `https://api.themoviedb.org/3/movie/upcoming?api_key=${API_KEY}&language=en-US&page=1`
};
//initialize an empty array to Promise.all on
const promiseArr = [];
//loop through movie URLs, call fetch + json()
for (const movies in allMovieURLs) {
//create an object that directly describes the data you get back from the url with a key
const obj = {
[movies]: fetch(allMovieURLs[movies]).then(res => res.json())
};
//push that object into an array so you can use Promise.all
promiseArr.push(obj);
Now what I have here is an array (promiseArr) of objects that have the correct key with the correct Promise stored inside of them.
My next plan was going to be to call Promise.all on them, but I need an array of promises. After I get the actual data back from the URL calls, I wanted to store them back in the object, to the correct corresponding key?
I've been stuck at this part for a couple hours now...any tips would be appreciated!
You can use async/await and Object.entries() to convert a JavaScript plain object to an array of arrays of key, value pairs
(async() => {
for (const [movie, url] of Object.entries(allMovieURLs)) {
try {
allMovieURLs[movie] = await fetch(url).then(res => res.json())
.catch(e => {throw e})
} catch(e) {
console.error(e)
}
}
})()
Don't call then when adding to promise array. Instead call it in promise all
const trending = fetch(trending);
...
Promise.all([trending, topRated, nowplaying, upcoming]).then(([trending, topRated, nowplaying, upcoming]) => {
movies.trending = trending.json();
....
});
You could just also store the promises themselves in another array.
function fetch() {
var time = Math.random() * 1E3;
return new Promise(function (res, rej) {
setTimeout(function () {
res(time);
}, time);
});
}
const API_KEY = "";
const allMovieURLs = {
trending: `https://api.themoviedb.org/3/trending/all/day?api_key=${API_KEY}`,
topRated: `https://api.themoviedb.org/3/movie/top_rated?api_key=${API_KEY}&language=en-US&page=1`,
nowPlaying: `https://api.themoviedb.org/3/movie/now_playing?api_key=${API_KEY}&language=en-US&page=1`,
upcoming: `https://api.themoviedb.org/3/movie/upcoming?api_key=${API_KEY}&language=en-US&page=1`
};
const promiseArr = [];
const promises = [];
for (const movies in allMovieURLs) {
const obj = { [movies]: undefined };
promises.push(fetch(allMovieURLs[movies]).then(res => obj[movies] = res));
promiseArr.push(obj);
}
Promise.all(promises).then(function () {
console.log(promiseArr);
});
If that is not feasible, you can do something like: Promise.all(promiseArr.map(obj => Object.values(obj)[0]))