In my web application, there are several components that will need access to the same data (in JSON). To avoid making unnecessary REST calls, I made a module that is supposed make a fetch request and store the result in a variable. On subsequent requests... if the data is available in the module, it will be directly returned (so only one network request is necessary).
For example:
var data_module = function(){
var data; //Module stores the json data in a variable
return{ //Returns an object that contains a public method accessible to external functions
get_json:function(){
if(data){ //If data already exists, then return a Promise object that immediately resolves with data
return Promise.resolve(data);
}
else{ //Else if data does not exist, make fetch request
fetch('/rest/url/endpoint', {credentials:'include'})
.then(function(response){
if(!response.ok){
throw new Error(response.statusText);
}
return response.json(); //Returns json of response
})
.then(function(json){
data = json; //Assigns data the value of json to store the result for subsequent requests
return Promise.resolve(data) //Returns a Promise that resolves with data
});
}
} //Public method that is supposed to provide access to data
}
}(); //Module will automatically execute
Outside of the module, I will try to access data like so:
some_dom_element.onclick = function(){ //Some html element is clicked and we need the data
data_module.get_json().then(function(json){
console.log(json); //However this never gets called
});
}
It does not work. Even though data_module's get_json function returns a Promise, the .then method does not get called outside of data_module. I was wondering if anyone can explain why this happens? (Or provide a general direction of how to modify the solution to achieve the goal of storing json results of fetch requests).
Thanks in advance!
You need to return the fetch
var data_module = (function() {
var data; //Module stores the json data in a variable
return { //Returns an object that contains a public method accessible to external functions
get_json: function() {
if (data) { //If data already exists, then return a Promise object that immediately resolves with data
console.log("** From CACHE **")
return Promise.resolve(data);
} else { //Else if data does not exist, make fetch request
// returning the fetch
return fetch('https://jsonplaceholder.typicode.com/posts/1', {
credentials: 'include'
})
.then(function(response) {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json(); //Returns json of response
})
.then(function(json) {
data = json;
return Promise.resolve(data) //Returns a Promise that resolves with data
});
}
} //Public method that is supposed to provide access to data
}
}()); //Module will automatically execute
//Outside of the module, I will try to access data like so:
var some_dom_element = document.getElementById("testBt")
some_dom_element.onclick = function() { //Some html element is clicked and we need the data
data_module.get_json().then(function(json) {
console.log(json); //However this never gets called
});
}
<button id="testBt">Test</button>
You could actually make the code a little shorter by storing the promise that response.json() returns and just returning it rather than storing the text and generating a new Promise every time you get it from the cache.
let data_module = (function() {
let data;
return {
get_json: () => {
if (!data) {
data = fetch('https://jsonplaceholder.typicode.com/posts/1', {
credentials: 'include'
})
.then(response => {
if (!response.ok) {
// clear data so next time it tries to contact the server again
data = undefined;
throw new Error(`${response.status}: ${response.statusText}`);
} else {
// return the promise that .json() returns so it is stored
// in data
return response.json();
}
});
}
return data;
}
};
}());
data_module.get_json().then(data => {
console.log(data);
});
data_module.get_json().then(data => {
console.log(data);
});
I used let and arrow functions => since any browser that supports fetch also supports those.
Related
When calling a function that returns a promise, comes back as undefined unless async operators are removed, then returns ZoneAwarePromise, but contains no data.
I know the query returns data when the function executes, it however does not seem to pass that data to the actual return part of the function call.
I have looked at several Stack questions that have not answered this question including this question:
Async/Await with Request-Promise returns Undefined
This is using a REST endpoint to pull data, the console.logs do show the data is correct, however return comes back as undefined
this.allPeople.forEach(async person => {
const dodString = await this.getRelatedRecords(person); //undefined
}
This is the main function that returns a promise / data
async getRelatedRecords(person) {
// function truncated for clarity
// ...
//
console.warn('This async should fire first');
selPeopleTable.relationships.forEach(relationship => {
allRelationshipQueries.push(
arcgisService.getRelatedTableData(
selPeopleTable.url, [person[oidField.name]], relationship.id, relationship.name),
);
});
await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
}).catch(function(data){
console.log('there might be data missing', data);
});
}
Removing the ASYNC operators cause the getRelatedRecords() to fire after the containing function and / or return a 'ZoneAwarePromise' which contains no data. I need getRelatedRecords() to fire first, then to run the rest of the code.
I can provide more snippets if need be.
Zone Aware Promise
When the Async operators are (I think) setup correctly
You need to return this as well:
await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
})
return in the above block is returning but all of this is in the scope of the arrow function which is then(allResults => { so you also need to return this function like this:
return await Promise.all(allRelationshipQueries).then(allResults => {
Approach #2:
Second way would be to store that into variable like this:
let dataToReturn = await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
}).catch(function(data){
console.log('there might be data missing', data);
});
return dataToReturn;
I am using axios to return a JSON object that is necessary for my module. This is a pretty big object, so I would like to use the singleton pattern to significantly reduce the requests for this object every time it is required.
For example:
// This variable holds the JSON object once it is retrieved by axios
var myJsonObject = null;
function fetchMyJsonObject() {
if (!myJsonObject) {
// use axios here to fetch
axios.get('/objects/my-json-object')
.then(response => {
myJsonObject = response.data;
});
}
return myJsonObject;
}
The problem here is obvious: axios' requests are asynchronous, so the return myJsonObject; line will run before the axios request is completed ,and before the myJsonObject variable is assigned the fetched data response.data;. So, myJsonObject will always be null.
I wish to only fetch the myJsonObject once and store it into a variable that can be served to other client modules if it already exists. If it does not exist, then I wish to fetch it and store it.
How can I restructure this function to achieve this?
Just return the promise object. In the client code, you just need to call on the promise object.
var getJsonPromise = null;
function fetchMyJsonObject() {
if (!getJsonPromise ) {
// use axios here to fetch
getJsonPromise = axios.get('/objects/my-json-object');
}
return getJsonPromise;
}
Client Code
fetchMyJsonObject();
getJsonPromise.then(response => // do whatever you want with json data);
What about using new ES syntax
// This variable holds the JSON object once it is retrieved by axios
var myJsonObject = null;
async function fetchMyJsonObject() {
try {
if (!myJsonObject) {
myJsonObject = await axios.get('/objects/my-json-object');
}
return myJsonObject;
}
catch(error) {
console.log(error)
}
}
I get some data from mongodb using mongoose find() and perform some validation on that data, but the problem is that this function is async and it does not wait for the data to be completed and execute the next code.
and when the next code is executed it enables to perform because it has null data. then i wrote my validation logic in the async function so that when data is available only then it move to next code but on every return it sends undefined data.
function isValidObject(obj) {
schemaData.find({}, (error, data) => { // calls data from db
var contactSchema = data; // this is data i need
if(//validation fails){
return "wrong data";// every time it returns undrfined on every
// condition
}
});
}
var validationResp = isValidObject(obj);
console.log(validationResp); // output is undefined
i also used "await" to wait for the data, but in that case it return [object promise] on every return statement
use async/await
In your case:
async function isValidObject(obj) {
let data = await schemaData.find({}); // wait until it resolve promise
//do your validaion
return data;
}
isValidObject(obj).then((validationResp)=>{console.log(validationResp)});
use the then() method it return a promise
var promise1 = new Promise(function(resolve, reject) {
resolve('Success!');
});
promise1.then(function(value) {
console.log(value);
// expected output: "Success!"
});
more details at MDN Using the then method
Are you familiar with "promises".You can use ".then(callback)". It will wait until async function is executed. And you can perform your validation in the callback function
User.findById(id)
.then(function(user) {
if (!user) {
return "here you return an error";
}
return "you return OK with payload from database";
})
.catch(next);
What is happening in your case is that when you assign var validationResp = isValidObject(obj); the function isValidObject(obj) has not returned anything and has only scheduled a callback(This is a very important concept when working with callbacks). As pointed out above you need to use Promises. The below is an example of your case as to how you can use Promises.
function isValidObject(obj){
return new Promise((resolve,reject) => {
schemaData.find({}, (error, data) => { // calls data from db
if(validation fails){
reject(error)// every time it returns undrfined on every
// condition
}else{
var contactSchema = data; // this is data i need
resolve(data)// You can resolve the promise with the data directly instead
// of assigning it to a variable or you can use (resolve(contactSchema))
}
})
})
}
After this when you want to use the data you can use do something like the below code snippet.
isValidObject(obj)
.then(result => {
// Use the result object here. This is the result from the Database
})
.catch(error => {
//Handle the Error
})
I've got this Node.js snippet.
var requestify = require('requestify');
// [...]
function remoterequest(url, data) {
requestify.post(url, data).then(function(response) {
var res = response.getBody();
// TODO use res to send back to the client the number of expected outputs
});
return true;
}
I need to return res content instead of true, back to the caller.
How can I do that?
In this case, the requestify's method is asyncronous, therefore, the returned value is not possible to be retrieved (since it's not generated yet).
How can I solve it? How can I send a synchronous HTTP POST request (even without requestify)?
you need to return a promise and use it in the then method of the promised returned by remoteRequest :
var requestify = require('requestify');
// [...]
function remoterequest(url, data) {
return requestify
.post(url, data)
.then((response) => response.getBody());
}
//....
remoteRequest('/foo', {bar: 'baz'}).then(res => {
//Do something with res...
});
Note that it still won't be a synchronous POST though, but you will be able to use response.getBody() when available, If this is what you wanted
You can refer to this discussion about how to use content returned from a promise How do I return the response from an asynchronous call?
As mentionned by #Logar, you can't use directly the content returned in your promise. You must call your method returning a promise first, and use .then to make the returned content available.
Example:
var requestify = require('requestify');
// [...]
// This function returns a promise, so you have to call it followed by `.then` to be able to use its returned content
function remoterequest(url, data) {
requestify
.post(url, data)
.then((response) => {
return response.getBody();
});
}
//....
//... Some other code here
//....
// Make a call to your function returning the promise
remoterequest('your-url-here', {data-to-pass-as-param})
.then((res) => { // Calling `.then` here to access the returned content from `remoterequest` function
// Now you can use `res` content here
});
I am trying to wrap the getstream API in an angular service (factory)
Here is my code:
.factory('FeedStream', function($http, $q) {
var client = stream.connect('xxxxxxxxxxx');
return {
feed : function() {
$http.get('/api/feed/auth/')
.success(function(auth) {
var user_feed = client.feed('user', auth.user, auth.token);
console.log(user_feed.get());
user_feed.get().then(function(data) {
console.log(data);
return data;
})
})
},
}
First I get the user_id and auth token from my server using the endpoint /api/feed/auth/. This data is returned in an angular promise.
Next, I use this data to call the getstream api to get the user_feed object. If I return this object (user_feed) it is undefined in the controller. If I print it to the console here in the service, it has the correct value. I've noticed that the print happens half a second or so after the return. Why is the assignment of this variable happening asynchronously?
Now if I call the get method on this user_feed object inside a console.log statement, a Javascript promise object is printed out. If I return user_feed.get() it returns undefined to the controller. If I call it here in the service like in my code above, and return the data object in the promise then statement, it returns undefined to the controller. However if I print the data object, it has the correct value.
Why can't I return any objects from this service? Am I missing some fundamental aspect of using promises?
You haven't returned feed promise object as well as data haven't been returned from feed method correctly. So for achieving the same thing do use .then over $http to maintain promise chaining
Code
return {
feed : function() {
//return `$http.get` promise
return $http.get('/api/feed/auth/')
.then(function(response) {
var auth = response.data;
var user_feed = client.feed('user', auth.user, auth.token);
console.log(user_feed.get());
//return `$http.get` to return internal promise
return user_feed.get().then(function(data) {
console.log(data);
return data;
})
})
},
}
Controller
FeedStream.feed().then(function(data){
console.log("data returned by user feed", data)
})