How can multiple unconnected async events await a single promise - javascript

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.

Related

Get properties from previously resolved promise?

I have an async function that returns an object after running fetch and .json(). I want to have more than one callback that take the object as input. Since I need to chain .then() to call the callback, I need to run the fetching function each time.
Is there a way to fetch once, and then have the callbacks do their thing with the output without having to refetch?
async function getData(){
const response = await fetch('api-url');
return await response.json();
}
function foo(data){
// do stuff
}
function bar(data){
// do stuff
}
const doFoo = getData().then(foo) // I don't want to run getData()
const doBar = getData().then(bar) // each time
I guess, I can save the output to a cache, but is there a way to do it with promises?
Any time you want to generate a value once and then use it multiple times: Store it in a variable.
const data = getData();
data.then(foo);
data.then(bar);
//to check the state of the promise
undefined === Promise.prototype.state && (Promise.prototype.state = function () {
const t = {};
return Promise.race([this, t]).then(v => (v === t) ? 'pending' : 'fulfilled', () => 'rejected');
});
function promiseWrapper(promiseFunction) {
let getPromise = Promise.reject();
let getFinalData = async function(force) {
let args = [].slice.call(arguments, 1);
//if force available, api will be called
if(force) {
return promiseFunction.apply(this, args);
}
let state = await this.getPromise.state();
//if state is rejected, trigger and fetch the data
'rejected' === state && (this.getPromise = promiseFunction.apply(this, args));
return this.getPromise;
}
//get method first method is force, rest will be treated as arguments
return {getPromise, get: getFinalData};
}
//instead of creating function pass the function in promiseWrapper
//promiseWrapper helps if somehow it is rejected, we wan recall the function without refreshing the page
var getData = promiseWrapper(async function() {
const response = await fetch('https://reqres.in/api/products/3');
return await response.json();
});
function foo(data) {
console.log('foo data\n', data)
}
function bar(data) {
console.log('bar data\n', data)
}
getData.get().then(foo) //It will trigger the api, because it is first time
getData.get(true).then(bar) //data by calling api
//getData.get(false).then(console.log) and getData.get().then(console.log), will act same

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

Node.js cache request data from method inside class object

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

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.

How to get data returned from fetch() promise?

I am having trouble wrapping my head around returning json data from a fetch() call in one function, and storing that result in a variable inside of another function. Here is where I am making the fetch() call to my API:
function checkUserHosting(hostEmail, callback) {
fetch('http://localhost:3001/activities/' + hostEmail)
.then((response) => {
response.json().then((data) => {
console.log(data);
return data;
}).catch((err) => {
console.log(err);
})
});
}
And this is how I am trying to get the returned result:
function getActivity() {
jsonData = activitiesActions.checkUserHosting(theEmail)
}
However, jsonData is always undefined here (which I am assuming is because the async fetch call has not finished yet when attempting to assign the returned value to jsonData. How do I wait long enough for the data to be returned from the fetch call so that I can properly store it inside of jsonData?
always return the promises too if you want it to work:
- checkUserHosting should return a promise
- in your case it return a promise which return the result data.
function checkUserHosting(hostEmail, callback) {
return fetch('http://localhost:3001/activities/' + hostEmail)
.then((response) => {
return response.json().then((data) => {
console.log(data);
return data;
}).catch((err) => {
console.log(err);
})
});
}
and capture it inside .then() in your main code:
function getActivity() {
let jsonData;
activitiesActions.checkUserHosting(theEmail).then((data) => {
jsonData = data;
}
}
EDIT:
Or even better, use the new syntax as #Senthil Balaji suggested:
const checkUserHosting = async (hostEmail, callback) => {
let hostEmailData = await fetch(`http://localhost:3001/activities/${hostEmail}`)
//use string literals
let hostEmailJson = await hostEmailData.json();
return hostEmailJson;
}
const getActivity = async () => {
let jsonData = await activitiesActions.checkUserHosting(theEmail);
//now you can directly use jsonData
}
You're partially right. It's because you're trying to get the result of this asynchronous call in a synchronous fashion. The only way to do this is the same way you deal with any other promise. Via a .then callback. So for your snippet:
function getActivity() {
return activitiesActions.checkUserHosting(theEmail).then((jsonData) => {
// Do things with jsonData
})
}
Any function that depends on an asynchronous operation must itself become asynchronous. So there's no escaping the use of .then for anything that requires the use of the checkUserHosting function.
You can make use of new ES6 and Es7 syntax and what others have written is also correct, but this can be more readable and clean,
you are trying to get aysnc value synchronously, here jsonData will be undefined because, you move to next line of execution before async function(checkUserHosting) is finish executing, this can be written as follows
const getActivity = async () => {
let jsonData = await activitiesActions.checkUserHosting(theEmail);
//now you can directly use jsonData
}
and you can write checkUserHosting in a different using new syntax like this
const checkUserHosting = async (hostEmail, callback) => {
let hostEmailData = await fetch(`http://localhost:3001/activities/${hostEmail}`)
//use string literals
let hostEmailJson = await hostEmailData.json();
return hostEmailJson;
}

Categories