Node.js cache request data from method inside class object - javascript

I am trying to cache the data I am receiving from a get request inside a class in Node.js by invoking a method when the class is instantiated. I want to run it only one time when I create a new instance of the class.
class GetSomeData {
constructor() {
this.storedData = '';
this.getData();
}
getData = async () => {
const allData = await axios.get(`URL`, config)
this.storedData = allData
}
}
let newInstance = new GetSomeData();
when I log newInstance.storedData I get ''.
I am receiving the data back from the get request but I am not able to store it in this.storedData.
For some reason this works:
class GetSomeData {
constructor() {
this.storedData = this.getData();
}
getData = async () => {
const allData = await axios.get(`URL`, config)
return allData
}
}
let newInstance = new GetSomeData();
when I log newInstance.storedData I get the actual data.
The second way should run the getData method every time I access newInstance.storedData but it actually runs it only one time when the new instance is being created.
I don't understand what I am missing.

It depends on where/when you call console.log.
Remember that JavaScript code execution never stops and that it remains single threaded and synchronous. Asynchronous behavior is achieved by queuing and handling tasks while interacting with external I/O interfaces (generally from the underlying OS).
Also remember that async functions always and implicitly return a promise once the first await statement is reached. Then code execution continues outside of the async function until the awaited promise is fulfilled.
In your first case, you are initializing your storedData property with an empty string, and that is what you get when you try to log it before the async request ends.
In your second case, you are actually logging a promise that will then be resolved with the fetched data and that is why you see the data.
const get = async () => {
return await new Promise((resolve) => {
setTimeout(() => {
resolve('DATA');
}, 3000);
});
};
class GetSomeData {
constructor() {
this.storedDataA = '';
this.storedDataB = this.getData();
}
getData = async () => {
this.storedDataA = await get();
return this.storedDataA;
}
}
const instance = new GetSomeData();
console.log(instance.storedDataA);
console.log(instance.storedDataB);
instance.storedDataB.then((storedDataB) => {
console.log(instance.storedDataA);
console.log(storedDataB);
});

In the first attempt, the constructor kicks off an async task and returns immediately; the code creating the instance runs right away, too early to see the result. In the second attempt, the constructor calls a method that returns after the async operation is complete.
Neither one is well-advised. Instead, don't anything async (or async and blocking) in the constructor, and place the async work in an instance method.
You can still have caching...
class MyClass {
constructor() {
}
async getData() {
if (!this.storedData) {
this.storedData = await get('url', config)
}
return this.storedData
}
}
The caller looks like this...
const myInstance = new MyClass()
const data = await myInstance.getData();
const dataButQuicker = await myInstance.getData(); // this will get cached data

Related

How to return a variable from function that gets variable from another function in JavaScript?

I have multiple functions that pass data from one to another. I moved one of the functions to the backend and receive data as API with Axios. Now I cannot manage to assign data from Axios to some local variable.
Simple code would be like:
function function1()
{
axios({get, url})
.then(response => {
globalVariable = response.data;
function2(globalVariable);
}
function function2(globalVariable)
{
const local = globalVariable;
return local;
}
And then inside of function3, I want to do:
function function3()
{
const from_local = function2()
from_local
}
When I try this I receive undefined result. Please help.
This is what promises are for. No need for globals or jumping through hoops to get the data out. Just remember to await any function that's async, (like axios) and annotate any function that contains an "await" as being async.
// note "async" because it contains await
async function backend() {
// note await because axios is async
const response = await axios({get, url});
return response.data;
}
// same thing up the calling chain
async function middleend() {
const local = await backend();
return local;
}
async function frontend() {
const local = await middleend();
console.log('ta da! here\'s the data', local);
}
It looks like you are looking for some sort of piping asynchronous operation. By piping I mean result of one function execution will be feed to another.
So basically function1 which is mimicking a axios operation here.
// making an api call
function function1() {
return fetch('https://jsonplaceholder.typicode.com/todos/1').then((d) =>
d.json()
);
}
// some random function
function function2(data) {
console.log(' Calling function 2 ');
return data?.title;
}
// some random function
function function3(data) {
console.log(' Calling function 3 ');
return `Hello ${data}`;
}
/** a function to resolve functions sequentially. The result of first
function will be input to another function.
Here ...fns is creating an array like object
so array operations can be performed here **/
const runAsynFunctions = (...fns) => {
return (args) => {
return fns.reduce((acc, curr) => {
return acc.then(curr);
}, Promise.resolve(args));
};
};
// calling runAsynFunctions with and passing list of
// functions which need to resolved sequentially
const doOperation = runAsynFunctions(function2, function3);
// resolving the api call first and the passing the result to
// other functions
function1().then(async(response) => {
const res = await doOperation(response);
console.log(res);
});

How to consume a promise and use the result later on with my code?

I'm new to async operations and js. Here is my question.
I have a Person class. I want to init Person instance using data that I get from an API call.
class Person {
constructor(data) {
this.data = data;
}
}
I am making an API call using Axios. I get a response and want to use it in my class.
const res = axios.get('https://findperson.com/api/david');
const david = new Person(res);
I understand that res is a promise at this stage, and that I need to consume it.
How should I do it?
How can I take the response and use it properly?
axios.get() return a promise of an object which contains the returned data, status, headers, etc...
async function getPerson() {
try {
const res = await axios.get('https://findperson.com/api/david');
const david = new Person(res.data);
// do something with david
} catch (error) {
console.log(error)
}
}
or
function getPerson() {
axios
.get('https://findperson.com/api/david')
.then(res => {
const david = new Person(res.data)
// do something with david
})
.catch(console.log)
}
Inside another async function, or at the top level of a module or at the REPL (in node 16.6+ or some earlier versions with the --experimental-repl-await feature enabled), you can just use await.
const res = await axios.get('https://findperson.com/api/david');
That will wait for the promise to be resolved and unwrap it to store the contained value in res.
If you want to get the value out of the world of async and into synchro-land, you have to do something with it via a callback function:
axios.get('https://findperson.com/api/david').then(
res => {
// do stuff with res here
});
... but don't be fooled; without an await, any code that comes after that axios.get call will run immediately without waiting for the callback. So you can't do something like copy res to a global var in the callback and then expect it to be set in subsequent code; it has to be callbacks all the way down.
You can do this:
axios.get('https://findperson.com/api/david').then(res => {
const david = new Person(res);
});
Or in async function: (See async await for javascript)
const res = await axios.get('https://findperson.com/api/david');
const david = new Person(res);

How can multiple unconnected async events await a single promise

I have a system where I need an id from a server to handle events. I should only fetch the id if/when the first event happens, but after that, I need to use the same id for each subsequent event. I know how to use async-await etc. so I have some code like this
var id = "";
async function handleEvent(e) {
if (! id ) {
let response = await fetch(URL)
if (response.ok) {
let json = await response.json();
id = json.id ;
}
}
// use id to handle event
}
But my problem is that I could receive multiple events before I receive a response, so I get multiple overlapping calls to fetch a new id.
How can I have multiple asynchronous calls to handleEvent, with the first one processing the fetch and any subsequent call waiting for it to complete to access the result?
Create a function to ensure you only make one request for the id using a lazy promise.
const URL = 'whatever'
let idPromise // so lazy 🦥
const getId = async () => {
const response = await fetch(URL)
if (!response.ok) {
throw response
}
return (await response.json()).id
}
const initialise = () {
if (!idPromise) {
idPromise = getId()
}
return idPromise
}
// and assuming you're using a module system
export default initialise
Now all you have to do is prefix any other call with initialise() to get the ID which will only happen once
import initialise from 'path/to/initialise'
async function handleEvent(e) {
const id = await initialise()
// do stuff with the ID
}
The currently accepted response relies on a global variable, which is not ideal. Another option is to use a class.
class IDManager {
getId(URL) {
if (this.id) {
return this.id;
}
this.id = fetch(URL)
return this.id
}
}
Then when you call getId, you simply await the result. If no previous request has been made, a network request will be sent. If there is already a pending request, every call will await the same result. If the promise is already resolved, you will get the result immediately.
const idManager = new IDManager();
async function handleEvent() {
const id = await idManager.getId(URL);
// Do stuff with the ID.
}
Not clear why your function is parameterized by "e".
I would write it in a more straightforward manner:
async function request(URL) {
let response = fetch(URL)
if (response.ok) {
let json = await response.json();
return json.id;
}
return false;
}
Then if the sequence of your calls matters write them one by one.
If not then you could use Promise.all (Promise.allSettled maybe) to run them all at once.
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
The solution to this turned out to be a little different from the previous answers. I thought I would post how I made it work in the end. The answers from #phil and #vaelin really helped me to figure this out.
Here was my solution...
class IDManager {
async fetchID (resolve,reject ) {
const response = await fetch( URL, { } ) ;
const id = await response.json() ;
resolve( id );
}
async getID() {
if ( this.id === undefined ) {
if ( this.promise === undefined ) {
var self = this;
this.promise = new Promise( this.fetchID ).then( function(id) { self.id = id;} );
}
await this.promise;
}
return this.id;
}
}
The problem was that awaiting the fetch the getID call took a couple of seconds. During that time there were often multiple calls to getID, all of which initiated another fetch. I avoided that by wrapping the fetch and response.json calls in another promise which was created instantly, and so avoided the duplicates.

How To 'Flush' A Jest Mock (without using await)

I come from the AngularJS world but now using React and Jest (with Jest Mock).
I'd like to do this....
test('should update internal tracking variable', async () => {
api.post = jest.fn().mock;
expect(obj.postCallFinished).toBe(false)
obj.begin() //this calls api.post internally with await
expect(obj.postCallFinished).toBe(false)
api.post.flush()
expect(obj.postCallFinished).toBe(true)
})
I don't want to use await in this instance on the obj.begin call. I need more fine grain control and want to check internal tracking variables so that I can slowly step through all the callbacks of my app (without breaking encapsulation on the functions). I need to do state based testing and it's important that I can slowly step through each blocking call in turn.
Please can someone help me figure out how I get control over the promise and slowly resolve the mock imperatively?
It sounds like begin is an async function that calls await on a series of async functions.
You can spy on the functions called within begin and retrieve the Promise that each returns by using mockfn.mock.results. Then you can await each of those Promises individually within the test to check the state at each step.
Here is a simple example to get you started:
class MyClass {
constructor() {
this.state = 0;
}
async first() {
await Promise.resolve(); // <= do something asynchronous
this.state = 1;
}
async second() {
await Promise.resolve(); // <= do something asynchronous
this.state = 2;
}
async begin() {
await this.first();
await this.second();
await Promise.resolve(); // <= do something asynchronous
this.state = 3;
}
}
test('walk through begin', async () => {
const firstSpy = jest.spyOn(MyClass.prototype, 'first');
const secondSpy = jest.spyOn(MyClass.prototype, 'second');
const instance = new MyClass();
const promise = instance.begin();
expect(instance.state).toBe(0); // Success!
await firstSpy.mock.results[0].value; // <= wait for the Promise returned by first
expect(instance.state).toBe(1); // Success!
await secondSpy.mock.results[0].value; // <= wait for the Promise returned by second
expect(instance.state).toBe(2); // Success!
await promise; // <= wait for the Promise returned by begin
expect(instance.state).toBe(3); // Success!
})

Async/Await not waiting for response, and returns Promise object

Using ES6, Classes, and Aync/Await...
The goal is to have an "Api" class that does async calls with fetch, and returns some data.... but even the basic foundation isn't working.
in the main js these snippet runs, which starts the chain of events:
let api = new Api();
let api_data = api.getLocation();
console.log(api_data);
getLocation method is the following, which would return some response/data. However, that data would theoretically be a fetch call to an API, which is "getTestVariable" for example that waits some time...
class Api {
getLocation = async () => {
var response = await this.getTestVariale();
console.log(response);
return response;
}
getTestVariale = () =>{
setTimeout(function(){
console.log("timeout done...");
return "this is the test variable";
},2000);
}
}
However, 1) the console.log(response) gives "undefined" because its not awaiting... and 2) back in the main js, api_data when logged, is some Promise object rather than the variable response
As the comment above states, setTimeout does not return a Promise, so getTestVariable has no return value.
Here's a slightly modified version of your code that will hopefully put you on the right track:
class Api {
getLocation = async () => {
var response = await this.getTestVariale();
console.log(response);
return response;
}
getTestVariale = () => {
return new Promise((resolve, reject) => {
if (thereIsError)
reject(Error('Error message'));
else
resolve({foo: 'bar'});
}
}
}
Drop a comment if I need to explain further I'd be happy to.
In getLocation you await for a value that will come from this.getTestVariable. In order for this to work this.getTestVariable must return a Promise; it can be done in two ways - making getTestVariable an async function or explicitly returning a Promise.
Since you're using setTimeout (which is not an async function) you're bound to use Promise. Here you go:
class Api {
async getLocation() {
return await this.getTestVariable();
}
getTestVariable() {
return new Promise((res, rej) => {
setTimeout(() => res('test'), 2000)
});
}
}
async function main() {
let api = new Api;
console.log('Trying to get the location...');
console.log('Voila, here it is: ', await api.getLocation());
}
main();
Looks quite ugly but there's no way you can achieve it if you use set timeout.
The main point is in resolution of getTestVariable with value you want it to return.
Quite an important remark: you can mark getTestVariable as an async function, it will ad an extra Promise level, but you still will have the desired result.

Categories