Ionic Data Storage wait for a promise to finish - javascript

I am using Data Storage from Ionic Framework. In my code, I have my auth guard which checks user info is stored or not. Code is :
this.storage.get('user_info').then((data) => {
let user_info = JSON.parse(data);
if(user_info == null)
{
this._userIsLoggedIn = false;
} else {
this._userIsLoggedIn = user_info.isLogin;
}
});
return this._userIsLoggedIn;
I am only getting the default value that I set. How do I wait for the promise to finish before it is returning?

If you using promises in a method and want to return the result of the promise, you should also promisify your method too. You can achieve this in two way:
Return a promise
Or use async-await
Approach 1 :
return new Promise( resolve => {
this.storage.get('user_info').then((data) => {
let user_info = JSON.parse(data);
if(user_info == null)
{
this._userIsLoggedIn = false;
} else {
this._userIsLoggedIn = user_info.isLogin;
}
return resolve(this._userIsLoggedIn);
});
});
Approach 2 (Which is cleaner):
const data = await this.storage.get('user_info');
let user_info = JSON.parse(data);
if(user_info == null)
{
this._userIsLoggedIn = false;
} else {
this._userIsLoggedIn = user_info.isLogin;
}
return this._userIsLoggedIn;
Also, note that you should modify your function with async keyword in order to use await.

Related

Twilio Method inside of promise does not work

Reading
https://www.twilio.com/blog/implementing-programmable-chat-php-laravel-vue-js
I try implement chat in my Laravel 8 / jQuery 3.5.1 / vue 2.6 app.
This docs has defined :
setupChannel(channel){
let vm = this;
return this.leaveCurrentChannel()
.then(function() {
return vm.initChannel(channel);
})
.then(function(_channel) {
return vm.joinChannel(_channel);
})
.then(this.initChannelEvents);
},
I want to extend joinChannel method, as I want to make checks if current logged user (laravel auth)
is already joined. I try to make it with promise and failes, as code inside of
vm.tc.messagingClient.getSubscribedUsers() is not run. I do
setupChannel(channel){
let vm = this;
return this.leaveCurrentChannel()
.then(function() {
return vm.initChannel(channel);
})
.then(function(_channel) {
let promise = new Promise((resolve, reject) => {
// debugger
vm.tc.messagingClient.getSubscribedUsers().then(function(users) {
// THESE CODE IS NOT RUN. If to uncomment console and debugging it is not triggered
// console.log('++ users::')
// console.log(users)
// debugger
for (let i = 0; i < users.length; i++) {
const user = users[i];
console.log('user.identity: ' + JSON.stringify(user.identity) );
// console.log('user: ' + JSON.stringify(user, null, 2) );
if( user.identity === vm.loggedUser.name ) {
resolve("result")
}
}
debugger // THESE CODE IS NOT RUN
resolve("error")
})
})
console.log('++ promise::')
console.log(promise) // I SEE this promise in pending state
promise
.then(
result => {
alert("result: " + result);
return _channel;
},
error => {
alert("error: " + error);
return vm.joinChannel(_channel);
}
)
// return vm.joinChannel(_channel);
})
.then(this.initChannelEvents);
If to run code
vm.tc.messagingClient.getSubscribedUsers().then(function(users)
...
inside of promise, it works ok and I got valid results.
What is wrong in my promise structure and how can I fix it?
MODIFIED BLOCK:
I try to follow your way with :
joinGeneralChannel() {
console.log('Attempting to join "general" chat channel...');
let vm = this;
if (this.tc.generalChannel == null) {
console.log('SETTING this.tc.messagingClient.createChannel')
vm.loadChannelList(vm.joinGeneralChannel);
}else {
// console.log('Found general channel:');
this.setupChannel(this.tc.generalChannel);
}
},
async setupChannel(channel) {
let vm = this
await this.leaveCurrentChannel()
const newChannel = await vm.initChannel(channel)
const subscribedUsers = vm.tc.messagingClient.getSubscribedUsers()
console.log('subscribedUsers::')
console.log(subscribedUsers)
let isUserJoined = false
for (let i = 0; i < subscribedUsers.length; i++) {
console.log('subscribedUsers[i] ' + JSON.stringify(subscribedUsers[i]) );
if( subscribedUsers[i].name === vm.loggedUser.name ) {
isUserJoined = true``
break
}
}
debugger
console.log('isUserJoined::')
console.log(isUserJoined)
But in the cosole of my browser I see :
Initialized channel General Channel
TeamChat.vue?e1c8:561 subscribedUsers::
TeamChat.vue?e1c8:562 PromiseĀ {<pending>}__proto__: Promise[[PromiseState]]: "pending"[[PromiseResult]]: undefined
TeamChat.vue?e1c8:573 isUserJoined::
looks like method getSubscribedUsers is asynchronous ?
Thanks!
Probably your Promise fails, that's why then() will never execute. To extend joinChannel method you can do something like this with async/await and ES6 syntax:
async setupChannel(channel) {
let vm = this;
try {
await this.leaveCurrentChannel();
const newChannel = await vm.initChannel(channel);
const users = await vm.tc.messagingClient.getSubscribedUsers();
const isUserJoined = users.some(({ name }) => name === vm.loggedUser.name);
const joinedChannel = isUserJoined ? newChannel : vm.joinChannel(_channel);
return this.initChannelEvents(joinedChannel);
} catch(err) {
// if some of promises below will fail, here you'll see details
console.log('Issue details here:', err);
}
}

The return value is undefined.Could tell what should I do to fix it?

The value of store is undefined.colud tell me what I did wrong.
Should I use Async and await or Promise ?
var store = db.read(collectionName, (status, data) => {
//database read function
if (status) {
names.listOfData = data;
var send = names;
console.log("send---->", send); // send value here is what I need to return.
return send; // Getting required data for send. Problem here is returning it and storing it in store.
} else {
return null;
}
});
console.log("store-->", store);
Here value of store is Undefined. No proper return
You can't write a result to store and immediately see it there after db.read because db.read does not wait when a callback will be finished and does not return data from it as a return value.
You should wrap this call to new Promise. It may look like this:
var store = await (new Promise(resolve, reject) => {
db.read(collectionName, (status, data) => {
//database read function
if (status) {
names.listOfData = data;
var send = names;
console.log("send---->", send); // send value here is what I need to return.
resolve(send);
} else {
resolve(null);
}
});
});
Please read more about asynchronous functions, async/await, Promise and how to convert a function with a callback into an async function.

firebase cloud functions oncall returns null

wondering what's the weird error.
I am using the onCall method from firebase cloud functions, but when I read it from my app it returns null value. I am trying to return some test data but it doesn't seem to be working. Am i returning the data wrongly?
index.js
exports.handleMassFollowAnalytics = functions.https.onCall((data, context) => {
const brandArray = data.brandArray;
const followed = data.followed;
let done = 0;
for (var i = 0; i < brandArray.length; i++) {
let brand = brandArray[i];
admin.database()
.ref(`brands/${brand}/followers`)
.transaction(function(post) {
if (post !== null) {
post--;
}
return post;
},
function(error, committed, snapshot) {
done++;
if (done === brandArray.length) {
// returning result.
return {
data: "testabc",
};
}
}
);
}
});
app.js
const handleMassFollowAnalytics = firebase
.functions()
.httpsCallable("handleMassFollowAnalytics");
handleMassFollowAnalytics({
brandArray: array,
followed: true,
}).then((result) => {
console.log("result: ", result) // returns null everytime
});
Your function needs to return a promise that resolves with the data to send to the client. Right now, your function returns nothing. The return statement inside the transaction callback is not returning from the main function.
Also, the code is ignoring the promises returned by the transactions you're performing. The final promise returned from the function must resolves only after all the other promises resolve.
So, I used Doug's information and arrived at the following answer, for reference to anyone in future.
This seems to return correctly for me.
Return individual promises
Return final promise
index.js
exports.handleMassFollowAnalytics = functions.https.onCall((data, context) => {
const brandArray = data.brandArray;
const followed = data.followed;
var promises = [];
for (var i = 0; i < brandArray.length; i++) {
let brand = brandArray[i];
promises.push(admin.database()
.ref(`brands/${brand}/followers`)
.transaction(function(post) {
if (post !== null) {
post--;
}
return post;
});
);
}
return Promise.all(promisess).then((result)=>{
return {
data: "testabc",
}
})
});

How to chain promises within nested for loops?

var verifyEmail = function (thisEmail){
return new Promise(
function (resolve, reject) {
quickemailverification.verify(thisEmail, function (err, response) {
// Print response object
console.log(response.body);
if (response.body["success"] == "true"){
var validity = response.body["result"];
if (validity == "valid"){
console.log("Email Valid!");
resolve(validity);
} else {
console.log("Email Invalid!")
resolve(validity);
}
} else {
var reason = new Error("API unsuccessful");
reject(reason);
}
});
}
);
};
var saveValidity = function (validity){
return new Promise(
function (resolve, reject){
if (validity == "valid"){
var state = true;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
console.log("Email ("+thisEmail+") verfication could not be saved" + error);
}
console.log("Email verification saved: " +thisEmail);
});
} else {
state = false;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
console.log("Email ("+thisEmail+") verfication could not be saved" + error);
}
console.log("Email verification saved: " +thisEmail);
});
}
}
);
};
admin.database().ref("/users_unverified/").once('value').then(function(snapshot) {
var snap = snapshot.val();
keys = Object.keys(snap);
for (var i = 0; i < 100; i++){
var emails = snap[keys[i]]["emails"];
if (emails){
for (var x = 0; x<emails.length; x++){
var thisEmail = emails[x]["email"];
var emailVerified = emails[x]["verified"];
if (emailVerified != true || emailVerified != false){
verifyEmail
.then(saveValidity)
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error){
console.log(error.message);
});
}
}
}
}
});
Above is the code I put together. I'm not all too convinced that it will work. I'm new to promises, so I'm trying to understand how to do this right.
The verifyEmail function should take in the email address from the firebase query in the third chunk of the code. The saveValidity function should take on the validity response from verifyEmail.
But, what I'm also worried about the nested for loop I have in the firebase query block. I'm looping through each user to validate their emails, but each user sometimes also has multiple emails. I'm worried that it will loop on to the next user before finishing checking all the emails of the previous user.
I'm also not sure if I can pass data into the promise functions the way I did.
Could definitely use some help here. Really trying hard to understand how this works.
First, you need to fix saveValidity() to always resolve or reject the promise and to pass in the other variables key and thisEmail that it references:
const saveValidity = function (validity, key, thisEmail){
return new Promise(
function (resolve, reject){
if (validity == "valid"){
let state = true;
admin.database().ref("/users_unverified/"+key+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
let msg = "Email ("+thisEmail+") verfication could not be saved" + error;
console.log(msg);
reject(new Error("Email ("+thisEmail+") verfication could not be saved" + error));
} else {
resolve("Email verification saved: " +thisEmail);
}
});
} else {
state = false;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
let msg = "Email ("+thisEmail+") verfication could not be saved" + error;
console.log(msg);
reject(new Error(msg));
} else {
resolve("Email verification saved: " +thisEmail);
}
});
}
}
);
};
Then, several changes are made to your main loop:
I assume we can run all the verifyEmail() calls in parallel since they don't appear to have anything to do with one another.
Change verifyEmail.then(...) to verifyEmail(thisEmail).then(...)` to actually call the function
Collect all the verifyEmail() promises in an array
Call Promise.all() on the array of promises to monitor when they are all done
Return value from .then() so we get the returned values in Promise.all()
rethrow in .catch() so promise stays rejected and will filter back to Promise.all(). You could eat errors here if you want to ignore them and continue with others.
Switch from var to let
Change from != to !== since it looks like your explicitly looking for a true or false value and don't want type casting.
Pass in the variables that saveValidity() needs.
Change logic when comparing emailVerified because what you had before was always true and thus probably not the right logic. I think what you want is to know when emailVerified is not yet set to true or to false which means you have to use &&, not ||.
Compare outer for loop with keys.length, not hard-coded value of 100.
And, here's the resulting code for the main nested for loop:
admin.database().ref("/users_unverified/").once('value').then(function(snapshot) {
let snap = snapshot.val();
let keys = Object.keys(snap);
let promises = [];
for (let i = 0; i < keys.length; i++){
let key = keys[i];
let emails = snap[key]["emails"];
if (emails){
for (let x = 0; x < emails.length; x++) {
let currentKey = key;
let thisEmail = emails[x]["email"];
let emailVerified = emails[x]["verified"];
if (emailVerified !== true && emailVerified !== false){
promises.push(verifyEmail(thisEmail).then(validity => {
return saveValidity(validity, currentKey, thisEmail);
}).then(function (fulfilled) {
console.log(fulfilled);
return fulfilled; // after logging return value so it stays the resolved value
}).catch(function (error) {
console.log(error.message);
throw error; // rethrow so promise stays rejected
}));
}
}
}
}
return Promise.all(promises);
}).then(results => {
// all results done here
}).catch(err => {
// error here
});
If ES2017 is available in your case, you can just use the keywords await and async to do that directly. Following is an example:
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
And you can read more about async/await here.
If you want to do that without async/await to achieve better browser compatibility, you can use Babel to do the pre-compile.
If you really want a lightwight implementation, you can use a function named chainPromiseThunks, or chain for short. This chain function accepts an Array of Thunks of Promises, And returns a new Thunk of Promise, Following is an one-line-implementation of chain:
const chain = thunks => thunks.reduce((r, a) => () => r().then(a));
And here is a usage demo:
const echo = x =>
new Promise(function(resolve) {
return setTimeout((function() {
console.log(x);
return resolve(x);
}), 1000);
})
;
const pThunks = [1,2,3,4,5].map(i => () => echo(i));
chain(pThunks)();

How to reuse promises?

I am trying to reuse the the data returned from promise here. But, the problem is, after the first call to checkPromise function, it immediately calls the second function, and the promise for the first function is not fulfilled, so it never returns any data, and hence it never enters the if clause. How do I reuse a promise?
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return new Promise(function (resolve, reject) {
request(url).spread(function(response, body) {
return resolve(body);
}).catch(function(err) {
console.error(err);
return reject(err);
});
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
}
checkPromise(url);
checkPromise(url);
You likely have a timing issue. Your apiCall() function is asynchronous. That means it finishes sometime later. As such, each time you call checkPromise(), all you're doing is starting a request and it finishes sometime later. So, you call it the first time and it starts a request (that has not finished yet). Then, your next call to checkPromise() gets called and it does it's if check before the first call has completed. Thus, it finds nothing in the cache yet.
Your code is running two requests in parallel, not one after the other.
If you actually want to wait until the first request is done before executing the second one, then you will have to actually structure your code to do that. You would need to make checkPromise() return a promise itself so code using it could known when it was actually done in order to execute something after it was done.
FYI, I don't see anything in your code that is actually related to reusing promises (which is something you cannot do because they are one-shot objects).
Here's one possible implementation:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
return Promise.resolve(rp);
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
}
}
checkPromise(url).then(function() {
checkPromise(url);
});
Significant changes:
Return the promise returned by request() rather than create yet another one.
Change checkPromise() so it always returns a promise whether the value is found in the cache or not so calling code can always work consistently.
Sequence the two checkPromise() calls so the first can finish before the second is executed.
A very different approach would be to actually wait on the cache if a result you are interested in is already being loaded. That could be done like this:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
// If it's a promise object in the cache, then loading
// If it's a value, then the value is already available
// Either way, we wrap it in a promise and return that
return Promise.resolve(obj[url]);
} else {
var p = apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
obj[url] = p;
return p;
}
}
checkPromise(url).then(function(result) {
// use result
});
checkPromise(url).then(function(result) {
// use result
});
few problems with your code, first in apiCall, you are doing a promise ant-pattern( no need for that new promise), second your checkPromise is doing a sync operation, so it must either return a promise or have a callback argument, so you code can be changed into:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
}).catch(function(err) {
console.error(err);
throw err;
});
}
function checkPromise(url) {
var promise = Promise.resolve();
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
return promise;
}
checkPromise(url).then(function(){
return checkPromise(url);
});
Given the way you are globally storing the result in 'obj[url]', it'd probably be easiest to do
function checkPromise(url) {
if (!obj[url]) obj[url] = apiCall(url);
obj[url].then(function(result) {
//do something
});
}
to basically make the request, if it hasn't already started, then attach a listener to the promise for when the result has loaded.
Here is the simplest example of how to prevent multiple API calls if there are multiple similar request for something (cache check for example)
var _cache = {
state: 0,
result: undefined,
getData: function(){
log('state: ' + this.state);
if(this.state === 0 ){ // not started
this.state = 1; // pending
this.promise = new Promise(function(resolve, reject) {
return (apiCall().then(data => { _cache.result = data; _cache.state = 2; resolve(_cache.result) }));
})
return this.promise;
}
else if(this.state === 1){ // pending
return this.promise;
}
else if(this.state === 2){// resolved
return Promise.resolve(this.result);
}
},
};
Simulating api call
function apiCall(){
return new Promise(function(resolve, reject) {
log('in promise')
setTimeout(() => {
log('promise resolving')
resolve(1);
}, 1000);
})
}
Making simultaneous requests.
_cache.getData().then(result => { log('first call outer: ' + result);
_cache.getData().then(result => { log('first call inner: ' + result); });
});
_cache.getData().then(result => { log('second call outer: ' + result);
_cache.getData().then(result => { log('second call inner: ' + result); });
});
Only one API call is maden. All others will wait for completion or use the resolved result if it already completed.

Categories

Resources