so basically i have a web application that retrieves data from firebase. Since it takes time to query, i used promises in javascript so that my code will execute at the right time. In function getDataFirebase, data is retrieved from firebase and is push to an array called Collect. So, after pushing 1 row, it query again to the firebase for another table of data to be collected then continues the loop. So, i create a promise before calling Firebase, and then resolving afterwards. But, im not doing that on getDataUsers.once("value"). There, i'm firing event and not waiting for it to return - the for-loop continues on, and all the callbacks processed later, but resolve is at the end of the for-loop, so its too late by then.
I used the async keyword in the when querying hoping that it would cause the for0loop to wait for that job to complete, but actually all it does here is causing the callback to return a promise - which is ingored by the on function. Its been a while already debugging and hoping that it would populate the execution at the right time. Someone pleasee help me? :(((
var promise = getDataFirebase();
promise.then(function () {
console.log(Collect);
console.log("firsst");
return getDataFirebaseUser();
}).then(function () {
console.log("Second");
});
function getDataFirebase() {
return new Promise(function (resolve, reject) {
refReview.on("value", function (snap) {
var data = snap.val();
for (var key in data) {
Collect.push({
"RevieweeName": data[key].revieweeID.firstname.concat(" ", data[key].revieweeID.lastname),
"ReviewerName": data[key].reviewerID.firstname.concat(" ", data[key].reviewerID.lastname),
rating: data[key].rating,
content: data[key].content,
keyOfReviewee: data[key].revieweeID.userID
})
var getDataToUsers = firebase.database().ref("users").child(data[key].revieweeID.userID);
getDataToUsers.once("value", async function (snap) {
var fnLn = snap.val();
var first = fnLn.isTerminated;
console.log("terminateStatus", first);
});
}//end of for loop
resolve();
}); //end of snap
});
}
Output of console according to the code is as follows:
Collect(array)
first
Second
terminateStatus, 1
it must be
Collect(array)
first
terminateStatus, 1
second
First of all, you misunderstood the concepts of async/await in javascript. Please, read this manual from mozilla and some examples to understand how to use it.
Coming back to your problem, adding "async" keyword to the callback function in your example does not change anything. Firebase will call that function when it fetches data the same way if its async or not. And before it fetches data, you call resolve at the end of for loop, completing promise. So the promise is resolved before you fetch the data, which is why you get terminateStatus at the end.
You could use Promise returned from once method, and await it so that it wont go out of for loop, until all the data fetched. To do that, you have add async to first callback. See this:
function getDataFirebase() {
return new Promise(function (resolve, reject) {
refReview.on("value", async function (snap) {
var data = snap.val();
for (var key in data) {
Collect.push({
"RevieweeName": data[key].revieweeID.firstname.concat(" ", data[key].revieweeID.lastname),
"ReviewerName": data[key].reviewerID.firstname.concat(" ", data[key].reviewerID.lastname),
rating: data[key].rating,
content: data[key].content,
keyOfReviewee: data[key].revieweeID.userID
})
var getDataToUsers = firebase.database().ref("users").child(data[key].revieweeID.userID);
var snapshot = await getDataToUsers.once("value");
var fnLn = snapshot.val();
var first = fnLn.isTerminated;
console.log("terminateStatus", first);
}//end of for loop
resolve();
}); //end of snap
});
}
However, this is still not very good piece of code since you call refReview.on method each time inside a different promise. You should either call it once in your app and attach a callback, or you should use refReview.once method if you just intend to fetch one document for each call.
Related
Working case:
async await is working fine when we call a asynchronous function and that function returning a promise resolve()
Not working case:
async await is not working for mongo DB queries
tried then(), async/await
I have 2 JS files.
In one.js file i am importing function which is in functionone.js
WORKING CASE:
When one.js looks like
var functiononestatus = transactions.functionone(req.session.email).then((came) => {
console.log(came); // getting `need to be done at first` message
console.log("exec next")
});
When functionone.js looks like
module.exports.functionone = functionone;
async function functionone(email) {
return await new Promise((resolve, reject) => {
resolve('need to be done at first')
});
});
NOT WORKING CASE (when mongo db query need to be executed):
When one.js looks like
var functiononestatus = transactions.functionone(req.session.email).then((came) => {
console.log(came); // getting undefined
console.log("exec next")
});
When functionone.js looks like
module.exports.functionone = functionone;
async function functionone(email) {
//mongo starts
var collection = await connection.get().collection('allinonestores');
await collection.find({
"email": email
}).toArray(async function(err, wallcheck) {
return await new Promise((resolve, reject) => {
resolve(wallcheck[0])
});
});
Quick clarification:
.collection('name') returns a Collection instance, not a Promise, so no need to await for it.
toArray() operates in two modes: either with a callback when a function is provided, either returns a Promise when no callback function is provided.
You're essentially expecting a Promise result out of toArray() while supplying a callback function, resulting in undefined, because callback takes priority and no promise is returned, due to the dual operation mode of toArray().
Also, toArray(callback) does not take an async function as callback.
Here's how your code should look like, for retrieving a collection:
const client = await MongoClient.connect('your mongodb url');
const db = client.db('your database name'); // No await here, because it returns a Db instance.
const collection = db.collection('allinonestores'); // No await here, because it returns a Collection instance.
and then, code for fetching results:
const db = <get db somehow>;
// You could even ditch the "async" keyword here,
// because you do not do/need any awaits inside the function.
// toArray() without a callback function argument already returns a promise.
async function functionOne(email) {
// Returns a Collection instance, not a Promise, so no need for await.
const collection = db.collection('allinonestore');
// Without a callback, toArray() returns a Promise.
// Because our functionOne is an "async" function, you do not need "await" for the return value.
return collection.find({"email": email}).toArray();
}
and code alternative, using callback:
const db = <get db somehow>;
// You could even ditch the "async" keyword here,
// because you do not do/need any awaits inside the function.
// You're forced to return a new Promise, in order to wrap the callback
// handling inside it
async function functionOne(email) {
// Returns a Collection instance, not a Promise, so no need for await.
const collection = db.collection('allinonestore');
// We need to create the promise outside the callback here.
return new Promise((resolve, reject) => {
db.find({"email": email}).toArray(function toArrayCallback(err, documents) {
if (!err) {
// No error occurred, so we can solve the promise now.
resolve(documents);
} else {
// Failed to execute the find query OR fetching results as array someway failed.
// Reject the promise.
reject(err);
}
});
});
}
Note: First of all i really need to thank #mihai Potra for the best answer.
Here we go
Case 1:
If it is a function which need to find documents and return from MongoDb as mihai mentioned, below answer is uber cool
const db = <get db somehow>;
async function functionOne(email) {
const collection = db.collection('allinonestore');
return collection.find({"email": email}).toArray();
}
case 2:
If there are nested functions which need to return values every time below ans will be the best as of my knowledge
-no need async/await keywords for every function or no need then()
function one(<parameter>) {
return new Promise(function(resolve, reject) {
const collection = connection.get().collection('<collection_name>');
const docs = collection.find({"Key": Value_fromFunction}).toArray( function (err, result) {
resolve(result[0]);
});
That's it, use resolve callback when ever it needed.
I am working with an Angular controller that will add an undetermined amount of questions from one or more surveys uploaded to the server to an existing survey(s) with same name, that's just for understanding, the uploading, handling in back-end and returning response works flawlessly, the real problem here is the specific part of just uploading the new questions, which has the following functions:
//Will save next question
const next_question = function (response, this_survey, response_survey, sur_questions, question) {
deferred = $q.defer();
new_question = new QuestionsService();
//Does a bunch of stuff with question object using all those arguments,
//which is not relevant here
if (check_existing_question(new_question, sur_questions)) {
deferred.resolve();
}
else {
new_question.$save({
actsern: new_question.actsern
}).then(function () {
deferred.resolve();
}).catch(function () {
deferred.reject();
});
}
return deferred.promise;
};
// Save the questions synchronously (wait for a promise to resolve/reject before saving next question)
async function save_question(response, this_survey, response_survey, sur_questions) {
// I want to store all promises in an array and return for future error handling
promises = [];
for (const quest in response_survey) {
// promise is undefined in console.log!!
promise = await next_question(response, this_survey, response_survey, sur_questions, quest);
console.log(promise);
//Commented because if not returns error "promises.push is not a function"
//promises.push(promise);
}
//Still undefined
console.log(promise);
return promise;
//The above is how I managed to make it work but what I really want is:
//return promises;
}
const set_survey_questions = function(response, this_survey, response_survey){
//Never mind this, unrelated to my problem
let sur_questions = QuestionsService.get({
//this_survey is survey object that is being uploaded to, questions and surveys are linked through actsern
actsern: this_survey.actsern
});
sur_questions.$promise.then(function () {
promises = save_question(response, this_survey, response_survey, sur_questions);
$q.all(promises).then(function () {
console.log("Uploaded successfully all questions");
}).catch(function (reason) {
console.log(reason);
});
});
};
The thing is that I have to save the questions synchronously, wait for one to resolve/reject before saving the next, which is why I am using an async loop function. Everything works fine, the questions are uploaded one after the order, everything goes to the server exactly as it's supposed to. The problem is that I would like to get the promises of next_question() from the async function to deal with their errors in the future and do some other stuff after they all have been resolved using the $q.all(), but the problem is that as far as I know the await should return a promise but it just returns undefined and keeps as undefined even after the loop has finished and all promises, supposedly, resolve.
I know the function next_question() works fine because if it is called directly (outside of async function, directly from set_survey_questions()) it saves the question and returns the promise just as it's supposed.
I'm not very experienced with angular or even javascript for that matter so any help or improvement you can think of is welcome.
As far as I know the await should return a promise but it just returns undefined
No. You should pass a promise to the await operator, it will then block execution of the async function until the promise is settled and return the result value of the promise.
The problem is that I would like to get the promises of next_question() from the async function to deal with their errors in the future
That doesn't seem to be compatible with your requirement of sequential saving. There are no multiple promises, there's only one active at a time. If any of them errors, the await will throw an exception and your loop will stop.
I think you really should simplify to
async function set_survey_questions(response, this_survey, response_survey) {
let sur_questions = QuestionsService.get({
actsern: this_survey.actsern
});
await sur_questions.$promise;
try {
for (const quest in response_survey) {
await next_question(response, this_survey, response_survey, sur_questions, quest);
}
console.log("Uploaded successfully all questions");
} catch (reason) {
console.log(reason);
}
}
Here is a simplified sample of my Promise code:
var sharedLocalStream = null;
// ...
function getVideoStream() {
return new Promise(function(resolve, reject) {
if (sharedLocalStream) {
console.log('sharedLocalStream is defined');
resolve(sharedLocalStream);
} else {
console.log('sharedLocalStream is null, requesting it');
navigator
.mediaDevices
.getUserMedia(constraints)
.then(function(stream) {
console.log('got local videostream', stream);
sharedLocalStream = stream;
resolve(sharedLocalStream);
})
.catch(reject);
}
});
}
I'm using this function asynchronously in several places.
The issue is related to the fact that function gets called at least twice, but in a second call promise never gets resolved/rejected.
This code works perfectly in Chrome. Also I tried to use Angular promises service $q, but it didn't work either.
What am I doing wrong and how to make this code work?
Also, I was thinking a lot about ways how I can avoid promises in this case and I have no choice because I forced to wait when user confirms mic&camera access request.
Update:
var constraints = {
audio: true,
video: true
};
Your code has concurrency issues if getVideoStream() is called twice. Because there is no forced queuing or sequencing when getVideoStream() is called a second time before the first call to it has updated the sharedLocalStream variable, you can easily end up with a situation where you create two streams before both calls are started before sharedLocalStream has a value.
This is an issue with the design of the code, not with the platform you are running it on. The usual way around this is to store the promise from the first operation into the shared variable. You then test to see if there's a promise already in there. If there is, you just return that promise.
If you cache the promise instead of the stream, you can do it as simply as this:
var sharedLocalStreamPromise = null;
function getVideoStream() {
// if we've already requested a local stream,
// return the promise who's fulfilled value is that stream
if (!sharedLocalStreamPromise) {
sharedLocalStreamPromise = navigator.mediaDevices.getUserMedia(constraints).catch(function(err) {
// clear the promise so we don't cache a rejected promise
sharedLocalStreamPromise = null;
throw err;
});
}
return sharedLocalStreamPromise;
}
The first call will initialize sharedLocalStreamPromise to a promise. When the second call (or any subsequent call) comes in, it will just return that same promise.
There is an edge case with this code which I'm thinking about. If the promise rejects and a second call to the same function has already occurred, it will also have the promise that rejects.
Without IIFE
let sharedLocalStreamPromise;
function getVideoStream() {
return sharedLocalStreamPromise = sharedLocalStreamPromise || navigator.mediaDevices.getUserMedia(constraints);
};
with IIFE
const getVideoStream = (() => {
let sharedLocalStreamPromise;
return () => sharedLocalStreamPromise = sharedLocalStreamPromise || navigator.mediaDevices.getUserMedia(constraints);
})();
Alterntively
var getVideoStream = () => {
const result = navigator.mediaDevices.getUserMedia(constraints);
getVideoStream = () => result;
return getVideoStream();
};
this last one creates a result (being the promise returned by getUserMedia) the first time it is called, and then overwrites itself to just return that result ... no conditionals in subsequent invocations of getVideoStream - so, theoretically "faster" (in this case, speed is a moot point though)
I understand using promises in simple scenarios but currently really confused on how to implement something when using a for loop and some updates to local sqlite database.
Code is as follows
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var q = $q.defer();
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
ajaxserviceAPI.postSurvey(survey).then(function(response) {
//from response update local database
surveyDataLayer.setLocalSurveyServerId(survey, response.result).then(function() {
q.resolve; // resolve promise - tried only doing this when last record also
})
});
})(survey) //pass in current survey used to pass in item into closure
}
return q.promise;
}).then(function() {
alert('Done'); // This never gets run
});
Any help or assistance would be appreciated. I'm probably struggling on how best to do async calls within loop which does another async call to update and then continue once completed.
at least promises have got me out of callback hell.
Cheers
This answer will get you laid at JS conferences (no guarantees though)
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
return Promise.all(Object.keys(surveys).map(function(key) {
var survey = surveys[key];
return ajaxserviceAPI.postSurvey(survey).then(function(response){
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
}));
}).then(function() {
alert('Done');
});
This should work (explanations in comments):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
// array to store promises
var promises = [];
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
var promise = ajaxserviceAPI.postSurvey(survey).then(function(response){
//returning this promise (I hope it's a promise) will replace the promise created by *then*
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
promises.push(promise);
})(survey); //pass in current survey used to pass in item into closure
}
// wait for all promises to resolve. If one fails nothing resolves.
return $q.all(promises);
}).then(function() {
alert('Done');
});
Awesome tutorial: http://ponyfoo.com/articles/es6-promises-in-depth
You basically want to wait for all of them to finish before resolving getSurveysToUpload, yes? In that case, you can return $q.all() in your getSurveysToUpload().then()
For example (not guaranteed working code, but you should get an idea):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var promises = [];
// This type of loop will not work in older IEs, if that's of any consideration to you
for (var item in surveys) {
var survey = surveys[item];
promises.push(ajaxserviceAPI.postSurvey(survey));
}
var allPromise = $q.all(promises)
.then(function(responses) {
// Again, we want to wait for the completion of all setLocalSurveyServerId calls
var promises = [];
for (var index = 0; index < responses.length; index++) {
var response = responses[index];
promises.push(surveyDataLayer.setLocalSurveyServerId(survey, response.result));
}
return $q.all(promises);
});
return allPromise;
}).then(function() {
alert('Done'); // This never gets run
});
I'm still struggling with promises, but making some progress thanks to the community here.
I have a simple JS function which queries a Parse database. It's supposed to return the array of results, but obviously due to the asynchronous nature of the query (hence the promises), the function returns before the results, leaving me with an undefined array.
What do I need to do to make this function wait for the result of the promise?
Here's my code:
function resultsByName(name)
{
var Card = Parse.Object.extend("Card");
var query = new Parse.Query(Card);
query.equalTo("name", name.toString());
var resultsArray = [];
var promise = query.find({
success: function(results) {
// results is an array of Parse.Object.
console.log(results);
//resultsArray = results;
return results;
},
error: function(error) {
// error is an instance of Parse.Error.
console.log("Error");
}
});
}
Instead of returning a resultsArray you return a promise for a results array and then then that on the call site - this has the added benefit of the caller knowing the function is performing asynchronous I/O. Coding concurrency in JavaScript is based on that - you might want to read this question to get a broader idea:
function resultsByName(name)
{
var Card = Parse.Object.extend("Card");
var query = new Parse.Query(Card);
query.equalTo("name", name.toString());
var resultsArray = [];
return query.find({});
}
// later
resultsByName("Some Name").then(function(results){
// access results here by chaining to the returned promise
});
You can see more examples of using parse promises with queries in Parse's own blog post about it.
What do I need to do to make this function wait for the result of the promise?
Use async/await (NOT Part of ECMA6, but
available for Chrome, Edge, Firefox and Safari since end of 2017, see canIuse)
MDN
async function waitForPromise() {
// let result = await any Promise, like:
let result = await Promise.resolve('this is a sample promise');
}
Added due to comment:
An async function always returns a Promise, and in TypeScript it would look like:
async function waitForPromise() {
// let result = await any Promise, like:
let result: Promise<string> = await Promise.resolve('this is a sample promise');
}
You're not actually using promises here. Parse lets you use callbacks or promises; your choice.
To use promises, do the following:
query.find().then(function() {
console.log("success!");
}, function() {
console.log("error");
});
Now, to execute stuff after the promise is complete, you can just execute it inside the promise callback inside the then() call. So far this would be exactly the same as regular callbacks.
To actually make good use of promises is when you chain them, like this:
query.find().then(function() {
console.log("success!");
return new Parse.Query(Obj).get("sOmE_oBjEcT");
}, function() {
console.log("error");
}).then(function() {
console.log("success on second callback!");
}, function() {
console.log("error on second callback");
});
You don't want to make the function wait, because JavaScript is intended to be non-blocking.
Rather return the promise at the end of the function, then the calling function can use the promise to get the server response.
var promise = query.find();
return promise;
//Or return query.find();