Get properties from previously resolved promise? - javascript

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

Related

Combine async function result in separate function in more efficient way

I have two async functions, that separately work on their own - they each return data if I call them singularly, but I can't get a result from the second when I try and combine them. Basically I'm pinging an API and if the first function returns a result, use that data. If it returns empty, run the second function and use that data. I'm getting the data through a node proxy (again these work fine separately).
Function 1 that checks for a live event:
async function getLive(){
const response = await fetch("video-live");
const getLiveData = await response.json();
return getLiveData;
}
Function 2 that should run if Function 1 returns empty:
async function getArchived() {
const response = await fetch("video-archived");
const getArchivedData = await response.json();
return getArchivedData;
}
The final function that applies the logic:
function showVideo(getLiveData, getArchivedData) {
if( (getLiveData) == "" ) {
console.log('live'+getLiveData);
} else {
console.log('no live'+getArchivedData);
}
}
Then I call them like this:
getLive().then(showVideo);
The above returns 'no live' but not any data from the getArchivedData. How do I combine these in an elegant, efficient way? Can I combine both getLive and getArchived into one function with .then()?
Per #MichaelM's code:
async function getLive(){
const response = await fetch("video-live");
const getLiveData = await response.json();
return getLiveData;
}
async function getArchived() {
const response = await fetch("video-archived");
const getArchivedData = await response.json();
return getArchivedData;
}
async function showVideo() {
const liveData = await getLive();
if(liveData == "") {
console.log('live' + getLiveData);
} else {
const archivedData = await getArchived();
console.log('no live' + archivedData);
}
}
"Uncaught (in promise) ReferenceError: getLiveData is not defined"
Try rewriting your showVideo() function to use getLive() and getArchived() directly, instead of passing the results into showVideo(). Like this:
async function showVideo() {
const liveData = await getLive();
if(liveData == "") {
console.log('live' + liveData);
} else {
const archivedData = await getArchived();
console.log('no live' + archivedData);
}
}
Then you just call showVideo() without the .then() statement.

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

Why am I not being able to use position 0 of an array with __proto__?

I am not able to use an array that I have in the code. I have no idea why __proto__ is there
It looks like a normal array, but when I want to access the values of position 0 (arr [0]), the console returns "undefined".
here is the javascript code:
const apiUrl = 'https://api.covid19api.com/dayone/country/south-africa';
async function getData(){
const response = await fetch(apiUrl);
const data = await response.json();
const arr = data.reduce((acc, cur) => cur.Confirmed ? [...acc, cur.Confirmed] : acc, []);
return arr;
}
var res = [];
getData().then( val => {
res.push(val);
})
console.log(res);
It is because the line console.log(res); is finished faster than the function getData().
const apiUrl = 'https://api.covid19api.com/dayone/country/south-africa';
async function getData(){
const response = await fetch(apiUrl);
const data = await response.json();
const arr = data.reduce((acc, cur) => cur.Confirmed ? [...acc, cur.Confirmed] : acc, []);
return arr;
}
var res = [];
getData().then( val => {
res.push(val);
console.log(res);
console.log(res[0]);
})
Put the statement inside the function getDate() or use .then() again to handle the data afterward.
const apiUrl = 'https://api.covid19api.com/dayone/country/south-africa';
async function getData(){
const response = await fetch(apiUrl);
const data = await response.json();
const arr = data.reduce((acc, cur) => cur.Confirmed ? [...acc, cur.Confirmed] : acc, []);
return arr;
}
var res = [];
function showData(callback){
getData().then( val => {
res.push(val);
callback();
});
}
showData( function(){
console.log(res[0])
});
Here's another solution, almost the same as TmasYIN's. you can add a callback function that's executed inside .then() function, and that callback function is defined when you execute showData.
Try this:
const apiUrl = 'https://api.covid19api.com/dayone/country/south-africa';
async function getData(){
const response = await fetch(apiUrl);
const data = await response.json();
const arr = data.reduce((acc, cur) => cur.Confirmed ? [...acc, cur.Confirmed] : acc, []);
return arr;
}
function showData(callback){
getData().then( val => {
res.push(val);
});
setTimeout(function() {
callback();
}, 1500);
}
showData( function(){
console.log(res[0])
});
proto is a built-in javascript object's inheritance pointer that points to its parent. (In javascript, function is also an object). In your example, getData() is inherited from AsyncFunction, so if you call getData().proto, it gives you an object of AsyncFunction. You can get the result after you give it some time to fetch data.
You can't access the result of a promise (i.e. its fulfilled value) immediately after registering a handler on a promise, because promise code never calls handlers provided to it synchronously.
When a promise becomes resolved or rejected, promise code puts jobs to execute handlers (and monitor their behavior) in a promise job queue, leaving executing tasks from the queue to the event loop manager.
This demonstration shows data is not transferred by then in the same call out from the event loop, but can be available after returning to the event loop:
let dataPromise = new Promise( resolve => resolve("hello, I'm data"));
var res=[];
res.ready = false;
dataPromise.then(data => {
res.push(data);
res.ready = true;
});
function processData() {
console.log(
"ready: %s", data: %s",
res.ready, res[0]);
}
processData(); // won't work
setTimeout( processData, 20); // will work
Data obtained using a promise is only available through the Promise interface. Code using the data can register a call back on the data promise by calling the then method of the promise, or using await before the promise inside an async function.
Obviously in a practical application, data also won't be available from a server request until after the server has responded, the browser received the reply and notified script code of the outcome.

NodeJS - Using Async/Await inside Array.map with 'if' statement

I've got a recursive function that loops over an object. It looks for keys with the name subcomponent (which is always an array) and performs an asynchronous function on each of subcomponent's children, the output of which is used to replace the child's data.
In the example below populateSubcomponent() is the async function.
Code:
async function doPopulate(data) {
Object.keys(data).map((key) => {
if (typeof data[key] === 'object') { // We want to do a recursive check on the data so are interested in any objects (arrays) at this point
if (key === 'subcomponent') { // If we have an object (array) with key `subcomponent` we want to populate each of its children
const promises = data[key].map(subcomponent => populateSubcomponent(subcomponent));
return Promise.all(promises).then((output) => {
data[key] = output;
console.log('1');
});
}
doPopulate(data[key]); // Check recursively
console.log('2');
}
console.log('3');
return data;
});
}
doPopulate(data);
My expectations are that each of the console.log numbers should fire sequentially, but instead I get 2, 3, then 1. As a result, the recursive functions runs before the async function has completed, therefore never replacing the child as intended; I get the correct result at 1 but it's not passed to 2 or 3.
How do I best incorporate the recursive doPopulate() call with the if statement?
I've looked at the following SO posts:
Using async/await inside for loop
Best way to call an async function within map?
Async/Await inside Array#map()
Use async await with Array.map
but I can't relate any of the answers to my own problem, mostly due to the fact that I've got an if statement within my recursive function and I'm not sure how to deal with that in context of the async stuff.
Edit
Thanks to everyone's comments I came up with the following:
async function doPopulate(data) {
const promisesOuter = Object.keys(data).map(async (key) => {
if (typeof data[key] === 'object') { // We want to do a recursive check on the data so are interested in any objects (arrays) at this point
if (key === 'subcomponent') { // If we have an object (array) with key `subcomponent` we want to populate each of its children
const promisesInner = data[key].map(subcomponent => populateSubcomponent(subcomponent));
data[key] = await Promise.all(promisesInner);
}
await doPopulate(data[key]); // Check recursively
}
return data;
});
return Promise.all(promisesOuter);
}
return doPopulate(data);
As this all happens within a NodeJS stream (using through2) I also needed to make the stream function async too:
const through = require('through2');
return through.obj(async (file, enc, done) => {
const data = JSON.parse(file.contents.toString());
await doPopulate(data);
file.contents = Buffer.from(JSON.stringify(data));
return done(null, file);
});
You will have to await every promise sequentially as long as the next promise relies on the previous one. If they can be done concurrently, you just have to aggregate the promises and await them together with Promise.all. By the way you only need the async keyword if you need to use await. Otherwise there's no reason to make the function async.
If you wanted to wait for each promise sequentially you would rewrite it like this:
function doPopulate(data) {
return Object.keys(data).map(async (key) => {
if (typeof data[key] === 'object') { // We want to do a recursive check on the data so are interested in any objects (arrays) at this point
if (key === 'subcomponent') { // If we have an object (array) with key `subcomponent` we want to populate each of its children
const promises = data[key].map((subcomponent) =>
populateSubcomponent(subcomponent)
);
data[key] = await Promise.all(promises);
console.log('1');
}
await doPopulate(data[key]); // Check recursively
console.log('2');
}
console.log('3');
return data;
});
}
Turns out I needed to return two sets of promises as #Bergi suggested in the comments:
async function doPopulate(data) {
const promisesOuter = Object.keys(data).map(async (key) => {
if (typeof data[key] === 'object') { // We want to do a recursive check on the data so are interested in any objects (arrays) at this point
if (key === 'subcomponent') { // If we have an object (array) with key `subcomponent` we want to populate each of its children
const promisesInner = data[key].map(subcomponent => populateSubcomponent(subcomponent));
data[key] = await Promise.all(promisesInner);
}
await doPopulate(data[key]); // Check recursively
}
return data;
});
return Promise.all(promisesOuter);
}
return doPopulate(data);
As this all happens within a NodeJS stream (using through2) I also needed to make the stream function async too:
return through.obj(async (file, enc, done) => {
const data = JSON.parse(file.contents.toString());
await doPopulate(data);
file.contents = Buffer.from(JSON.stringify(data));
return done(null, file);
});

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