Using Axios.js in a Singleton pattern to serve an object once - javascript

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)
}
}

Related

How to retrieve object from JSON in nodejs?

Having this code:
const fs = require('fs')
const file = 'books.json';
class Book{
constructor(code) {
this._code = code;
}
get code() {
return this._code;
}
set code(value) {
this._code = value;
}
}
async function writeBooks(){
const data = JSON.stringify([new Book('c1'), new Book('c2')]);
await fs.promises.writeFile(file, data, 'utf8');
}
async function getBook(code){
try{
const data = await fs.promises.readFile(file);
const array = JSON.parse(data);
return array.find(b => b.code === code);
} catch (err){
console.log(err)
}
}
writeBooks();
getBook('c1').then(b => console.log(b));
I am getting undefined (instead of the expecting book object).
How to get the object (the above problem)
If async function always returns promise, how can I then return object for the client, instead of him having to call then() from the getBook(code)?
do I need to await for the fs.promises.writeFile()? as I am doing in writeBooks()? As fas as I understand the async/await now, is that the return value from await function is the data or error. But since the writeFile() does not returns anything, or error at most (as opposed to readFile()) why would I want to await for no data?
Actually the root of problem is not about async/awaits or promises. The problem is trying to write an array to a json file. If you write your json data like the code snippet below (as a key-value pair), your problem is solved.
{"1": [new Book('c1').code, new Book('c2').code]} //as a key-value pair
const fs = require('fs')
const file = 'books.json';
class Book{
constructor(code) {
this._code = code;
}
get code() {
return this._code;
}
set code(value) {
this._code = value;
}
}
async function writeBooks(){
const data = JSON.stringify({"1": [new Book('c1').code, new Book('c2').code]});
await fs.promises.writeFile(file, data, 'utf8');
}
async function getBook(code){
try{
const data = await fs.promises.readFile(file);
const dataOnJsonFile = JSON.parse(data);
return dataOnJsonFile["1"];
} catch (err){
console.log(err)
}
}
writeBooks();
getBook('c1').then(b => console.log(b));
The above problem is that the Books returned from JSON.parse have only data, not methods, and thus I cannot get the code via get code(){}, but only as public parameter of class Book as book._code, which however breaks encapsulation (convetion is that _[propery] is private, and there should be appropriate getters/setters). So I made the properties public (and broke encapsulation), because I still don't know, how to assign methods to object created from JSON.
No, the result of async is always Promise. You cannot unwrap it inside async, the client will always have to unwrap it. (so await fs.promises.WriteFile() will unwrap it, but then immediately wrap it back, before async function returns.
as explained above.

NodeJS: Chain functions automatically in a promise?

I'm currently fetching data from an API and I need to do multiple GET requests (using axios). After all those GET requests are completed, I return a resolved promise.
However, I need to do these GET requests automatically based on an array list:
function do_api_get_requests() {
return promise = new Promise(function(resolve, reject) {
API_IDs = [0, 1, 2];
axios.get('https://my.api.com/' + API_IDs[0])
.then(data => {
// Do something with data
axios.get('https://my.api.com/' + API_IDs[1])
.then(data => {
// Do something with data
axios.get('https://my.api.com/' + API_IDs[2])
.then(data => {
// Do something with data
// Finished, resolve
resolve("success");
}
}
}
}
}
This works but the problem is API_IDs isn't always going to be the same array, it will change. So I'm not sure how to chain these requests automatically.
Since you said it may be a variable length array and you show sequencing the requests, you can just loop through the array using async/await:
async function do_api_get_requests(API_IDS) {
for (let id of API_IDS) {
const data = await axios.get(`https://my.api.com/${id}`);
// do something with data here
}
return "success";
}
And, since you said the list of API ids would be variable, I made it a parameter that you can pass into the function.
If you wanted to run all the API requests in parallel (which might be OK for a small array, but might be trouble for a large array) and you don't need to run them in a specific order, you can do this:
function do_api_get_requests(API_IDS) {
return Promise.all(API_IDS.map(async (id) => {
const data = await axios.get(`https://my.api.com/${id}`);
// do something with data here for this request
})).then(() => {
// make resolved value be "success"
return "success";
});
}
Depending upon your circumstances, you could also use Promise.allSettled(). Since you don't show getting results back, it's not clear whether that would be useful or not.
You can use Promise.all() method to do all API requests at the same time, and resolve when all of them resolves.
function do_api_get_requests() {
const API_IDs = [0, 1, 2];
let promises = [];
for (const id of API_IDS) {
promises.push(axios.get(`https://my.api.com/${id}`));
}
return Promise.all(promises);
}
If you use Bluebird.js (a better promise library, and faster than the in-built Promise), you can use Promise.each(), Promise.mapSeries(), or Promisme.reduce() to do what you want.
http://bluebirdjs.com

Async Promise returns undefined or zone aware promise

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;

Return html response to calling function in nodejs

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
});

Setting value of variable within promise chain in Javascript

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.

Categories