I've looked through several promise syntax, but I'm really not understanding it (including the ones that had examples). Most of them resolved a variable, but mine is a bit more complex, and I can't seem to get it running right.
I'm using Firebase to get the current user, and want it to run the next lines AFTER I get the user.
Here's my code:
componentDidMount() {
var promise = new Promise ((resolve, reject) => {
var user = fire.auth().currentUser};
resolve(
if(user) {
console.log('Favorites: requesting favorites');
fire.database().ref('/favourites/' + user.uid).once('value').then(function (snapshot) {
var recipes_obj = snapshot.val();
let recipes = [];
for (let id in recipes_obj) {
let recipe = recipes_obj[id];
recipe.id = id;
recipes.push(recipe);
console.log("recipes: ", recipes)
}
console.log("recipes outside", recipes);
this.setState({ recipes: recipes });
}.bind(this));
} else {
console.log('Favorites: no user')
}
)
}
I've also tried it like this
componentDidMount() {
var user = new Promise ((resolve, reject) => {
resolve(fire.auth().currentUser).then(() => {
if(user) {
console.log('Favorites: requesting favorites');
fire.database().ref('/favourites/' + user.uid).once('value').then(function (snapshot) {
var recipes_obj = snapshot.val();
let recipes = [];
for (let id in recipes_obj) {
let recipe = recipes_obj[id];
recipe.id = id;
recipes.push(recipe);
console.log("recipes: ", recipes)
}
console.log("recipes outside", recipes);
this.setState({ recipes: recipes });
}.bind(this));
} else {
console.log('Favorites: no user')
}
})
})
var user = fire.auth().currentUser
This code is not asynchronous. When this line executes, you have the user immediately in var user. You don't need to use a promise to do something in response to getting the user - you already have it.
The only time you need to create a new Promise object and resolve it is when you have some work that run asynchronously and you have no other promise to indicate when that work is complete.
In your code you also don't return your new promise to the caller. This means the promise is useless. Unless you hand a promise off to another bit of code that operates in response to its resolution, it doesn't really help the situation. In other words, your promise is not doing anything here.
The only bit of code that's asynchronous in your code is the fetch from the database:
fire.database().ref('/favourites/' + user.uid).once('value')
once() returns a promise. In your code, you're calling the then() method on that promise to do more work after the result is available.
Seems like your not actually calling your promise anywhere just declaring it inside your componentDidMount? Try something like this.. BTW, never used firebase so not sure on the below api use.
componentDidMount() {
var promise = new Promise ((resolve, reject) => {
let user = ()=> fire.auth().currentUser
resolve(user)
})
promise().then((user)=>{
if(user) {
return fire.database().ref(`/favourites/${user.uid}`).once('value')
}
})
.then((snapshot)=>{
//Do other stuff with snapshot
})
}
Related
Currently I have many concurrent identical calls to my backend, differing only on an ID field:
getData(1).then(...) // Each from a React component in a UI framework, so difficult to aggregate here
getData(2).then(...)
getData(3).then(...)
// creates n HTTP requests... inefficient
function getData(id: number): Promise<Data> {
return backend.getData(id);
}
This is wasteful as I make more calls. I'd like to keep my getData() calls, but then aggregate them into a single getDatas() call to my backend, then return all the results to the callers. I have more control over my backend than the UI framework, so I can easily add a getDatas() call on it. The question is how to "mux" the JS calls into one backend call, the "demux" the result into the caller's promises.
const cache = Map<number, Promise<Data>>()
let requestedIds = []
let timeout = null;
// creates just 1 http request (per 100ms)... efficient!
function getData(id: number): Promise<Data> {
if (cache.has(id)) {
return cache;
}
requestedIds.push(id)
if (timeout == null) {
timeout = setTimeout(() => {
backend.getDatas(requestedIds).then((datas: Data[]) => {
// TODO: somehow populate many different promises in cache??? but how?
requestedIds = []
timeout = null
}
}, 100)
}
return ???
}
In Java I would create a Map<int, CompletableFuture> and upon finishing my backend request, I would look up the CompletableFuture and call complete(data) on it. But I think in JS Promises can't be created without an explicit result being passed in.
Can I do this in JS with Promises?
A little unclear on what your end goal looks like. I imagine you could loop through your calls as needed; Perhaps something like:
for (let x in cache){
if (x.has(id))
return x;
}
//OR
for (let x=0; x<id.length;x++){
getData(id[x])
}
Might work. You may be able to add a timing method into the mix if needed.
Not sure what your backend consists of, but I do know GraphQL is a good system for making multiple calls.
It may be ultimately better to handle them all in one request, rather than multiple calls.
The cache can be a regular object mapping ids to promise resolution functions and the promise to which they belong.
// cache maps ids to { resolve, reject, promise, requested }
// resolve and reject belong to the promise, requested is a bool for bookkeeping
const cache = {};
You might need to fire only once, but here I suggest setInterval to regularly check the cache for unresolved requests:
// keep the return value, and stop polling with clearInterval()
// if you really only need one batch, change setInterval to setTimeout
function startGetBatch() {
return setInterval(getBatch, 100);
}
The business logic calls only getData() which just hands out (and caches) promises, like this:
function getData(id) {
if (cache[id]) return cache[id].promise;
cache[id] = {};
const promise = new Promise((resolve, reject) => {
Object.assign(cache[id], { resolve, reject });
});
cache[id].promise = promise;
cache[id].requested = false;
return cache[id].promise;
}
By saving the promise along with the resolver and rejecter, we're also implementing the cache, since the resolved promise will provide the thing it resolved to via its then() method.
getBatch() asks the server in a batch for the not-yet-requested getData() ids, and invokes the corresponding resolve/reject functions:
function getBatch() {
// for any
const ids = [];
Object.keys(cache).forEach(id => {
if (!cache[id].requested) {
cache[id].requested = true;
ids.push(id);
}
});
return backend.getDatas(ids).then(datas => {
Object.keys(datas).forEach(id => {
cache[id].resolve(datas[id]);
})
}).catch(error => {
Object.keys(datas).forEach(id => {
cache[id].reject(error);
delete cache[id]; // so we can retry
})
})
}
The caller side looks like this:
// start polling
const interval = startGetBatch();
// in the business logic
getData(5).then(result => console.log('the result of 5 is:', result));
getData(6).then(result => console.log('the result of 6 is:', result));
// sometime later...
getData(5).then(result => {
// if the promise for an id has resolved, then-ing it still works, resolving again to the -- now cached -- result
console.log('the result of 5 is:', result)
});
// later, whenever we're done
// (no need for this if you change setInterval to setTimeout)
clearInterval(interval);
I think I've found a solution:
interface PromiseContainer {
resolve;
reject;
}
const requests: Map<number, PromiseContainer<Data>> = new Map();
let timeout: number | null = null;
function getData(id: number) {
const promise = new Promise<Data>((resolve, reject) => requests.set(id, { resolve, reject }))
if (timeout == null) {
timeout = setTimeout(() => {
backend.getDatas([...requests.keys()]).then(datas => {
for (let [id, data] of Object.entries(datas)) {
requests.get(Number(id)).resolve(data)
requests.delete(Number(id))
}
}).catch(e => {
Object.values(requests).map(promise => promise.reject(e))
})
timeout = null
}, 100)
}
return promise;
}
The key was figuring out I could extract the (resolve, reject) from a promise, store them, then retrieve and call them later.
I am trying to write a series of AJAX requests into a dictionary.
I am attempting to use promises for this, however I am either writing the promise syntax incorrectly, or what I think may be happening is that the function is actually completing (for loop is done, and AJAX requests sent) but the AJAX requests are still not returned. Therefore this is still returning an empty dictionary.
let dict = {};
let activeMachines = ["41", "42", "43"];
let dataPromise = new Promise (function (resolve, reject)
{
for (let i = 0; i < activeMachines.length; i++)
{
let machineID = activeMachines[i]
let getAPIData = new XMLHttpRequest();
let url = 'http://127.0.0.1:8000/processes/apidata/' +machineID + '/';
getAPIData.open('GET', url);
getAPIData.send();
getAPIData.onload = function()
{
let APIData = JSON.parse(getAPIData.responseText);
dict['machine_' + machineID] = APIData[0].author_id;
dict['temp' + machineID] = APIData[0].tempData; //get value
dict['humid' + machineID] = APIData[0].humidData;
timeValue = String((APIData[0].dateTime));
dict['time' + machineID] = new Date(timeValue);
console.log("done");
}
}
resolve();
});
dataPromise.then(function() {console.log(dict);});
Is there a way to "sense" when all of the XMLHTTPRequests have returned?
#Rafael's answer will work, but it doesn't illuminate much about what's going wrong, since you're trying to grok the concept of Promises and write one yourself.
Fundamentally I think your approach has two missteps: 1. creating a single Promise that handles calls to all of your arbitrary list of "activeMachines", and 2. putting your resolve() call in the wrong place.
Usually a Promise looks like this:
const myPromise = new Promise(function(resolve, reject) {
doSomeAsyncWork(function(result) {
// Some kind of async call with a callback function or somesuch...
resolve(result);
});
}).then(data => {
// Do something with the final result
console.log(data);
});
You can simulate some kind of arbitrary asynchronous work with setTimeout():
const myPromise = new Promise(function(resolve, reject) {
// Resolve with "Done!" after 5 seconds
setTimeout(() => {
resolve("Done!");
}, 5000);
}).then(data => {
console.log(data); // "Done!"
});
However your original code puts the resolve() call in a weird place, and doesn't even pass it any data. It looks sorta equivalent to this:
const myPromise = new Promise(function(resolve, reject) {
// Resolve with "Done!" after 5 seconds
setTimeout(() => {
// Doing some work here instead of resolving...
}, 5000);
resolve();
}).then(data => {
console.log(data); // This would be "undefined"
});
Where you're doing a console.log("done"); in your original code is actually where you should be doing a resolve(someData);!
You're also trying to do side effect work inside of your Promise's async function stuff, which is really weird and contrary to how a Promise is supposed to work. The promise is supposed to go off and do its async work, and then resolve with the resulting data -- literally with the .then() chain.
Also, instead of doing multiple asynchronous calls inside of your Promise, you should generalize it so it is reusable and encapsulates only a single network request. That way you can fire off multiple asynchronous Promises, wait for them all to resolve, and then do something.
const activeMachines = ["41", "42", "43"];
// Make a reusable function that returns a single Promise
function fetchAPI(num) {
return new Promise(function(resolve, reject) {
const getAPIData = new XMLHttpRequest();
const url = "http://127.0.0.1:8000/processes/apidata/" + num + "/";
getAPIData.open("GET", url);
getAPIData.send();
getAPIData.onload = function() {
const APIData = JSON.parse(getAPIData.responseText);
const resolveData = {};
resolveData["machine_" + num] = APIData[0].author_id;
resolveData["temp" + num] = APIData[0].tempData; //get value
resolveData["humid" + num] = APIData[0].humidData;
timeValue = String(APIData[0].dateTime);
resolveData["time" + num] = new Date(timeValue);
resolve(resolveData);
};
});
}
// Promise.all() will resolve once all Promises in its array have also resolved
Promise.all(
activeMachines.map(ea => {
return fetchAPI(ea);
})
).then(data => {
// All of your network Promises have completed!
// The value of "data" here will be an array of all your network results
});
The fetch() API is great and you should learn to use that also -- but only once you understand the theory and practice behind how Promises actually operate. :)
Here's an example of the Fetch API which uses Promises by default:
let m_ids = [1,2,3,4];
let forks = m_ids.map(m => fetch(`http://127.0.0.1:8000/processes/apidata/${m}`));
let joined = Promise.all(forks);
joined
.then(files => console.log('all done', files))
.catch(error => console.error(error));
I hope this helps!
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.
I have a sequence of function calls, connected with ES6 promises. Apparently, there is something wrong with this implementation, as API calls to the endpoint are not returning anything and the browser is stuck waiting for a response.
Please advise.
module.exports.insertTreatmentDetails = function (req, res) {
var doctorId = 10000
var departmentId = 10000
var procedureid = 10000
var hospitalSchema = new hospitalModel();
var p = new Promise(function (resolve, reject) {
counterSchema.getNext('Treatment.doctor.doctorId', collection, function (doctorId) {
doctorId = doctorId;
})
counterSchema.getNext('Treatment.departmentId', collection, function (departmentId) {
departmentId = departmentId
})
counterSchema.getNext('Treatment.procedureid', collection, function (procedureid) {
procedureid = procedureid
})
}).then(function () {
setData()
}).then(function (){
hospitalSchema.save(function (error, data) {
if (error) {
logger.error("Error while inserting record : - " + error)
return res.json({ "Message": error.message.split(":")[2].trim() });
}
else {
return res.json({ "Message": "Data got inserted successfully" });
}
});
});
};
The short answer is that you aren't calling resolve or reject inside the first promise in your chain. The promise remains in a pending state. Mozilla has a good basic explanation of promises.
How to Fix
It appears that you want to retrieve doctorId, departmentId, and procedureId before calling setData. You could try to wrap all three calls in one promise, checking whether all three have returned something in each callback, but the ideal is to have one promise per asynchronous task.
If it's feasible to alter counterSchema.getNext, you could have that function return a promise instead of accepting a callback. If not, I would recommend wrapping each call in its own promise. To keep most true to what your code currently looks like, that could look like this:
const doctorPromise = new Promise((resolve, reject) =>
counterSchema.getNext('Treatment.doctor.doctorId', collection, id => {
doctorId = id;
resolve();
}));
Then you could replace the first promise with a call to Promise.all:
var p = Promise.all([doctorPromise, departmentPromise, procedurePromise])
.then(setData)
.then(/* ... */);
Promises allow you to pass a value through to the next step, so if you wanted to get rid of your broadly-scoped variables (or set them in the same step where you call setData), you could just pass resolve as your callback to counterSchema.getNext and collect the values in the next step (also how you'd want to do it if you have counterSchema.getNext return a promise:
Promise.all([/* ... */])
.then(([doctorID, departmentID, procedureID]) => {
// If you aren't changing `setData`
doctorId = doctorID;
departmentId = departmentID;
procedureid = procedureID;
setData();
// If you are changing `setData`
setData(doctorID, departmentID, procedureID);
}).then(/* ... */).catch(/* I would recommend adding error handling */);
I am trying to make a Firebase database call inside of a loop and an outer firebase call. The inner Firebase database call is using data returned from the outer firebase call and the loop, which is why it is run within the outer one. The results then should be set into the state.
Problem
The value that is being retrieved in the inner Firebase database call is not being set in the state.
Theory
Since Firebase database calls are asynchronous, my guess is that the inner Firebase database call does not finish before the loop completes and sets the state.
Therefore, I created a promise for the inner Firebase database call, so that the loop would wait for the call to finish before moving onto the next item.
However, the value retrieved is still not being set.
Does anyone know why the loop does not wait for promise containing call to Firebase database?
MY ATTEMPT
userRef.on("value", function(snapshot) {
var snap = [];
// loop through each branch received from firebase
snapshot.forEach(function(data) {
var firstThingsFirst = data.val().firstThingsFirst;
var someID = data.val().someID;
var myPromise = new Promise(function(resolve, reject) {
userRef.child('somechild').child(someID).once('value').then(function(newSnapshot) {
console.log("newSnapshot = (below)");
console.log(newSnapshot.val());
resolve(newSnapshot.val());
}, function(error) {
// Something went wrong.
console.error("error (below)");
console.error(error);
reject("noValueFound")
});
});
var someValue = "";
myPromise.then(function(valueRetrieved) {
console.log(".then of promise is running...");
console.log("valueRetrieved = (below)");
console.log(valueRetrieved);
someValue = this.checkUndefined(valueRetrieved);
}.bind(this));
var array = {"firstThingsFirst": firstThingsFirst, "someValue": someValue};
snap.push(array);
});
this.setState({
snapshots: snap
});
}.bind(this));
Alternative Attempt:
userRef.on("value", function(snapshot) {
var snap = [];
// loop through each branch received from firebase
snapshot.forEach(function(data) {
var firstThingsFirst = data.val().firstThingsFirst;
var someID = data.val().someID;
var someValue = this.fetchValueByID(someID);
var array = {"firstThingsFirst": firstThingsFirst, "someValue": someValue};
snap.push(array);
});
this.setState({
snapshots: snap
});
}.bind(this));
fetchValueByID(someID) {
userProfileRef.child('someChild').child(someID).once('value').then(function(snapshot) {
console.log("snapshot (fetchValueByID) = (below)");
console.log(snapshot.val());
return snapshot.val();
})
}
I have also tried the approaches recommended by Firebase:
https://firebase.googleblog.com/2016/01/keeping-our-promises-and-callbacks_76.html
Thanks in advance.
Does anyone know why the loop does not wait for promise containing call to Firebase database?
The reason for this is that you require setState to be called after all the fetches are done. But, your code doesn't do anything to wait. You just carry on with the loop and once it's done, call setState. You never really know whether your fetches completed or not. You need a way to wait for all the the fetches. In a nutshell, there is a problem because of mixing of synchronous and asynchronous code.
You can try this. The idea is to map all the fetchValueByID (I've added a return in the beginning) calls into an array of promises and then wait for all of them to resolve (using Promise.all) before doing setState
userRef.on("value", function(snapshot) {
// loop through each branch received from firebase
// AND map to array of promises
var promises = [];
snapshot.forEach(function(data) {
var firstThingsFirst = data.val().firstThingsFirst;
var someID = data.val().someID;
promises.push(this.fetchValueByID(someID).then(function(someValue) {
return {
"firstThingsFirst": firstThingsFirst,
"someValue": someValue
};
}));
});
// Wait for all promises to resolve
Promise.all(promises).then(function(results) {
this.setState({
snapshots: results
});
}.bind(this))
}.bind(this));
fetchValueByID(someID) {
// Notice the return here
return userProfileRef.child('someChild').child(someID).once('value').then(function(snapshot) {
console.log("snapshot (fetchValueByID) = (below)");
console.log(snapshot.val());
return snapshot.val();
})
}
I've faked all the possible data and converted my solution to a simple-to-understand snippet below
var promises = [];
// Faking the snapshot
[{
a: 1,
b: 10
}, {
a: 2,
b: 20
}].forEach(function(data) {
var firstThingsFirst = data.a
var someID = data.b
promises.push(fetchValueByID(someID).then(function(someValue) {
return {
"firstThingsFirst": firstThingsFirst,
"someValue": someValue
};
}));
});
// Wait for all promises to resolve
Promise.all(promises).then(function(results) {
console.log(results);
});
function fetchValueByID(someID) {
// Dummy Promise resolution
// Notice the return here
return new Promise(function(resolve, reject) {
setTimeout(function() {
// Dummy manipulation
resolve(someID * 100);
});
}).then(function(snapshot) {
console.log("snapshot (fetchValueByID) = (below)");
console.log(snapshot);
return snapshot;
})
}