Why does .json() return a promise?
Why does
response.json
return a promise?
Because you receive the response
as soon as all headers have arrived. Calling .json()
gets you another promise for the body of the http response that is yet to be loaded. See also Why is the response object from JavaScript fetch API a promise?.
Why do I get the value if I return the promise from the
then
handler?
Because that's how promises work. The ability to return promises from the callback and get them adopted is their most relevant feature, it makes them chainable without nesting.
You can use
fetch(url).then(response =>
response.json().then(data => ({
data: data,
status: response.status
})
).then(res => {
console.log(res.status, res.data.title)
}));
or any other of the approaches to access previous promise results in a .then() chain to get the response status after having awaited the json body.
Why does Body.json() return a Promise?
Your second snippet doesn't work because response.json()
aka body.json()
doesn't resolve instantly.
This is because body.JSON()
streams
and returns
a Response
using a Promise
asynchronously; which must then be captured by a then()
callback in order to read / manipulate.
Such is the nature of Promises
.
However, such a syntactic flow can still be achieved by leveraging async
await
.
fetch('https://freegeoip.net/json/8.8.8.8')
.then(async (response) => {
Object.keys(await response.json()).forEach((key) => {
console.log("got " + key)
})
})
JavaScript fetch API - Why does response.json() return a promise object (instead of JSON)?
because response.json() returns another promise (which is within your function body)
https://developer.mozilla.org/en-US/docs/Web/API/Body/json
Return json object from resolved promise and assign it after it is loaded
React components must render synchronously. This is how React was designed; it cannot and will never change. Any way you hear of to render asynchronously is an abstraction that caches old render results and returns them synchronously. For this reason, you'll never be able to do what your code indicates.
However, I suspect that you don't actually care if the function is called before the data loads (which is what your code suggests), but rather that you want it called and to display a loading menu until the asynchronous operation completes and gives you its result. This is a common problem in React and is solved by having an initial empty state that you later set.
import React, { useState } from 'react';
function MyComponent() {
// Since you're using TypeScript, you can type this by
// setting the generic parameter
// e.g. useState<Product[] | null>(null);
const [products, setProducts] = useState(null);
listProducts().then(function(loadedProducts) {
setProducts(loadedProducts);
});
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>text: {products !== null && JSON.stringify(products)}</Text>
</View>
);
}
To make this more ES6:
import React, { useState } from 'react';
const MyComponent = () => {
const [products, setProducts] = useState(null);
listProducts().then(setProducts);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>text: {products && JSON.stringify(products)}</Text>
</View>
);
}
There's just one problem with this: every time MyComponent
is called, the data is loaded. You may want this in some circumstances, but for the vast majority of the time, you only want to reload the products when something changes. This is where the useEffect
hook comes in. It accepts a function that will only be called if the dependency list has changed.
useEffect(() => {
// This is called once on the first render and only
// called again whenever either dep1 or dep2 changes
// Note that a change in dep1 or dep2 does not necessarily
// mean your component will be called again (i.e. be
// rerendered). That only happens if dep1 or dep2 came
// from a useState hook, because to change a value from
// a useState hook, you must call the setter, which tells
// React to rerender.
console.log(dep1 + dep2);
}, [dep1, dep2]);
If you add the current minute to the dependency list, the product list will update at most every minute.
useEffect(() => {
listProducts().then(setProducts);
}, [
// Time in milliseconds rounded down to the minute
Math.floor(Date.now() / (60 * 1000))
]);
If you only want to call the function once, make the dependency list empty. If you want to call it after every render, don't specify a dependency list at all (i.e. useEffect(callback)
). Read more here.
A few other things: your App
class is pointless. You may be used to object-oriented programming from languages like Java and C#, but modern JavaScript avoids classes as much as is reasonable. Moreover, you don't need to extend React.Component
because you aren't ever rendering the class with React. I'd recommend moving the functions out of the classes. In addition, you seem unsure as to how Promises work. They are called asynchronously, the callback is called way after the enclosing function finishes unless you use async/await. I'll refactor this for you, but you really shouldn't be taking on something this difficult without the basics. Try this Promise guide for a start, then learn how async/await make it easy to avoid infinite .thens.
const getProducts = async () => {
const data = await listProducts();
// typeof isn't a function
console.log(typeof data, data);
}
const listProducts = async () => {
// Create the initDB() function the way I did this
const db = await initDB();
const favdrinks = await new Promise((resolve, reject) => {
db.transaction(async tx => {
const [tx, results] = await tx.executeSql(
'SELECT p.favorites FROM drinks p',
[]
);
const favdrinks = [];
console.log("Query completed");
var len = results.rows.length;
for (let i = 0; i < len; i++) {
let row = results.rows.item(i);
console.log(`Drinks favorited: ${row.favorites}`)
const { favorites } = row;
favdrinks.push({
favorites
});
}
console.log(favdrinks);
resolve(favdrinks);
})
});
// Refactor this function as well
await closeDatabase(db);
return favdrinks;
}
Putting it all together:
import React, { useState, useEffect } from 'react';
const listProducts = async () => {
const db = await initDB();
return new Promise((resolve, reject) => {
db.transaction(async tx => {
const [tx, results] = await tx.executeSql(
'SELECT p.favorites FROM drinks p',
[]
);
const favdrinks = [];
var len = results.rows.length;
for (let i = 0; i < len; i++) {
let row = results.rows.item(i);
const { favorites } = row;
favdrinks.push({
favorites
});
}
resolve(favdrinks);
})
});
await closeDatabase(db);
return favdrinks;
}
const MyComponent = () => {
const [products, setProducts] = useState(null);
useEffect(() => {
listProducts().then(setProducts);
}, []); // empty dependency list means never update
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>text: {products && JSON.stringify(products)}</Text>
</View>
);
}
EDIT: Seeing your comment on another answer, you aren't able to remove the class logic. I think you may be misunderstanding the requirements of whatever framework you are using, but if you're seriously unable to avoid it, you should create the object of the class you're using inside the useEffect
callback.
Why my json is returning attached with Promise in nodejs function?
The function with async
keyword in front of function definition returns the Promise
type value.
So to get the value, you need to use await
keyword like
let Val = await fileName.load();
Uncaught in promise while waiting for json response
You might think about having one function to get, parse, and return the JSON from the API (no need to wrap your fetch
in a promise as it already returns one), and use your polling function to check the returned data. If it's correct call one function, otherwise poll getData
again. If there's an API error log that instead.
I've used async/await
here but the principles are the same.
// Simulates an API
// If the random number is a modulo of 5 set data
// to an object with a key/value pair, otherwise
// it set it to an empty object. If the random number
// is a modulo of 9 send an error, otherwise send the data.
function mockFetch() {
return new Promise((res, rej) => {
const rnd = Math.floor(Math.random() * 20);
const data = rnd % 5 === 0 ? { name: 'Bob' } : {};
setTimeout(() => {
if (rnd % 9 === 0) rej('Connection error');
res(JSON.stringify(data));
}, 2000);
});
}
// `getData` simply gets a response from the API and parses it.
// I had to use JSON.parse here rather that await response.json()
// because I'm not using the actual fetch API.
async function getData() {
const response = await mockFetch();
return JSON.parse(response);
}
// The `poll` function does all the heavy-lifting
// first we initialise `count`
async function poll(count = 1) {
console.log(`Polling ${count}`);
// Try and get some data. If it's not an empty object
// log the name (or in your case call loadGraph),
// otherwise poll the API again after two seconds.
// We wrap everything in a `try/catch`.
try {
const data = await getData();
if (data && data.name) {
console.log(data.name); // loadGraph
} else {
setTimeout(poll, 2000, ++count);
}
// If the API sends an error log that instead
// and poll again after five seconds
} catch (err) {
console.log(`${err}. Polling again in 5 seconds.`);
setTimeout(poll, 5000, 1);
}
}
poll();
How to get the JSON from promise
You could await the response:
fetch(url, options).then(async (response) => {
const data = await response.json();
console.log(data)
})
Related Topics
Group Array of Object Nesting Some of the Keys With Specific Names
How to List the Properties of a JavaScript Object
Check If a Value Is an Object in JavaScript
How to Provide Named Parameters in a Function Call in JavaScript
Read :Hover Pseudo Class with JavaScript
Error: No Firebase App '[Default]' Has Been Created - Call Firebase App.Initializeapp()
How to Properly Reuse Connection to Mongodb Across Nodejs Application and Modules
Cross-Browser JavaScript Xml Parsing
Use of .Apply() With 'New' Operator. Is This Possible
Why Is It Necessary to Set the Prototype Constructor
JavaScript Add Leading Zeroes to Date
How to Chain and Share Prior Results With Promises
Black and White Text Based on Background Image with CSS
How to Determine If ::Before Is Applied to an Element