I am using Ionic 2 with SQLite. I am getting an error, and I suspect it is due to me not using Promises correctly.
I get the following error:
ERROR REFRESHING CHATS: {}
TypeError {stack: (...), message: "Cannot read property 'executeSql'
of undefined"}
ts
private database = new SQLite();
public openDatabase(): Promise<Array<Message>> {
let promise: Promise<Array<Message>> = new Promise<Array<Message>>(resolve => {
if (this.database) {
let promiseChats: Promise<any> = this.refreshChats();
let promiseMessages: Promise<any> = this.refreshMessages();
Promise.all([promiseChats, promiseMessages]).then(() => { resolve(this.messages) });
} else {
this.database.openDatabase({
name: "data.db",
location: "default"
}).then(() => {
let promiseChats: Promise<any> = this.refreshChats();
let promiseMessages: Promise<any> = this.refreshMessages();
Promise.all([promiseChats, promiseMessages]).then(() => { resolve(this.messages) });
}, (error) => {
console.log("OPEN ERROR: ", error);
});
}
});
return promise;
}
public refreshChats(): Promise<any> {
return this.database.executeSql("SELECT * FROM chats", [])
.then((chatData) => {
this.chats = [];
if (chatData.rows.length > 0) {
for (var i = 0; i < chatData.rows.length; i++) {
this.populateChat(chatData, i);
}
}
return this.chats;
})
.catch(error => {
console.log("ERROR REFRESHING CHATS: " + JSON.stringify(error));
console.log(error);
});
}
When I debug the code, and break on this.database.executeSql, this.database is not undefined. Also, you can see there is a check on this.database to see if it's undefined before too. Yet, the debugger steps out the Promise into the error, reporting the error shown.
Also, after the Promise finishes, the this.chats are populated. So I am very confused.
If anyone can suggest what I am doing incorrectly, I would appreciate it.
UPDATE
I have updated the code as follows, but still get the same error:
public openDatabase(): Promise<Array<Message>> {
let promise: Promise<Array<Message>> = new Promise<Array<Message>>(resolve => {
if (this.database) {
Promise.all([this.refreshChats(), this.refreshMessages()]).then(() => { resolve(this.messages) });
} else {
this.database.openDatabase({
name: "data.db",
location: "default"
}).then(() => {
Promise.all([this.refreshChats(), this.refreshMessages()]).then(() => { resolve(this.messages) });
}, (error) => {
console.log("OPEN ERROR: ", error);
});
}
});
return promise;
}
public refreshChats(): Promise<any> {
return this.database.executeSql("SELECT * FROM chats", [])
.then((chatData) => {
let promises: Array<any> = [];
this.chats = [];
if (chatData.rows.length > 0) {
for (var i = 0; i < chatData.rows.length; i++) {
promises.push(this.populateChat(chatData.rows.item(i)));
}
}
Promise.all(promises).then(() => {
return this.chats;
});
})
.catch(error => {
console.log("ERROR REFRESHING CHATS: " + JSON.stringify(error));
console.log(error);
});
}
You are employing a bit of a Promise antipattern which I will describe to you. Although I can't see a reason it would cause your problem, it may help with debugging.
It's known as the explicit-construction anti-pattern and essentially it is when you create a new Promise unnecessarily.
For instance in your code, both of your functions create a new Promise at the start, where they could instead just return the promise used later;
public refreshChats(): Promise<Array<Chat>> {
if (!this.database || this.database === null) {
console.log('ERROR refreshing chats: database = ' + this.database);
}
return this.database.executeSql("SELECT * FROM chats", [])
.then((chatData) => {
this.chats = [];
if (chatData.rows.length > 0) {
for (var i = 0; i < chatData.rows.length; i++) {
this.populateChat(chatData, i);
}
}
return this.chats;
})
.catch(error => {
console.log("ERROR REFRESHING CHATS: " + JSON.stringify(error));
console.log(error);
});
}
This may help to break up your error so that you can see where it originates. If you produce any further information, ping me and I will update my answer.
Related
I want to get balance data from firebase. But it must be real-time.
I write the code below. The console output is "Balance: 0". After this output, it gets firebase data and writes it. So, before data come from firebase, the code continues and get balance function returns 0. I can't compare balance because of that. Actual data comes after that.
checkBalance() {
let balance = this.authService.getBalance()
result = this.authService.getBalance() >= this.price ? true : false
console.log("Balance: " + balance);
return result;
}
AuthService:
getBalance() {
let docRef = this.angularFirestore.collection(`users`).doc(JSON.parse(localStorage.getItem('user')).uid);
docRef.get().toPromise().then(function (doc) {
if (doc.exists) {
console.log(doc.data());
console.log(doc.data().balance);
return parseFloat(doc.data().balance);
} else {
// doc.data() will be undefined in this case
console.log("Doc can not find!");
return 0.0;
}
}).catch(function (error) {
console.log("Error getting document:", error);
return 0.0;
});
return 0.0;
}
Original Answer
Steps
Mark getBalance method async, and make it return your Promise
Mark checkBalance method async
Change the first line as follows: let balance = await this.authService.getBalance()
Sample
async getBalance() {
const docRef = this.angularFirestore.collection(`users`).doc(JSON.parse(localStorage.getItem('user')).uid);
return docRef.get().toPromise().then(function (doc) {
if (doc.exists) {
console.log(doc.data());
console.log(doc.data().balance);
return parseFloat(doc.data().balance);
} else {
// doc.data() will be undefined in this case
console.log("Doc can not find!");
return 0.0;
}
}).catch(function (error) {
console.log("Error getting document:", error);
return 0.0;
});
}
async checkBalance() {
const balance = await this.authService.getBalance();
const result = balance >= this.price ? true : false;
console.log("Balance: " + balance);
return result;
}
JS Reference
https://javascript.info/async-await
Update For Angular 12 or Greater
Deprecation of toPromise in rxjs 7
Now instead of toPromise you have to use lastValueFrom or firstValueFrom from rxjs
import { firstValueFrom} from "rxjs";
async getBalance() {
const docRef = this.angularFirestore.collection(`users`).doc(JSON.parse(localStorage.getItem('user')).uid);
return firstValueFrom(docRef.get()).then(function (doc) {
if (doc.exists) {
console.log(doc.data());
console.log(doc.data().balance);
return parseFloat(doc.data().balance);
} else {
// doc.data() will be undefined in this case
console.log("Doc can not find!");
return 0.0;
}
}).catch(function (error) {
console.log("Error getting document:", error);
return 0.0;
});
}
Rxjs Reference
https://rxjs.dev/deprecations/to-promise
I am developing a SPFx WebPart using TypeScript.
I have a function to get a team by name (get() returns also a promise):
public getTeamChannelByName(teamId: string, channelName: string) {
return new Promise<MicrosoftGraph.Channel>(async (resolve, reject) => {
this.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient) =>{
client
.api(`/teams/${teamId}/channels`)
.filter(`displayName eq '${channelName}'`)
.version("beta")
.get((error, response: any) => {
if ( response.value.length == 1) {
const channels: MicrosoftGraph.Channel[] = response.value;
resolve(channels[0]);
} else if (response.value.length < 1) {
reject(new Error("No team found with the configured name"));
} else {
reject(new Error("Error XY"));
}
});
})
.catch(error => {
console.log(error);
reject(error);
});
});
}
I call this function like this:
public getConversations(teamName: string, channelName: string, messageLimitTopics: number = 0, messageLimitResponses: number = 0) {
return new Promise<any>(async (resolve, reject) => {
try {
this.getTeamGroupByName(teamName)
.then((teamGroup: MicrosoftGraph.Group) => {
const teamId: string = teamGroup.id;
this.getTeamChannelByName(teamId, channelName)
.then((teamChannel: MicrosoftGraph.Channel) => {
const channelId: string = teamChannel.id;
this.getChannelTopicMessages(teamId, channelId, messageLimitTopics)
.then((messages: MicrosoftGraph.Message[]) => {
const numberOfMessages = messages.length;
... // omitted
});
});
});
} catch(error) {
reject(error);
}
});
}
And this getConversations() function itself is called from my webpart code:
public getConversations() {
if (this.props.teamName && this.props.teamName.length > 0 &&
this.props.channelName && this.props.channelName.length > 0) {
GraphService.getConversations(this.props.teamName, this.props.channelName, this.props.messageLimitTopics, this.props.messageLimitResponses)
.then((conversations) => {
.. // omitted
})
.catch((err: Error) => {
console.log(err);
this.setState({errorMessage: err.message});
});
} else {
// Mandatory settings are missing
this.setState({errorMessage: strings.MandatorySettingsMissing});
}
}
So, as you can see, above, I want to write out the error (message) I receive from the reject inside the getConversations() functions. The problem is, that I don't receive this rejection with the error, but in the console I see the following:
Uncaught Error: No team found with the configured name
I added the .catch() blocks you see above inside getTeamChannelByName() but this doesn't get hit during debugging.
Haven't worked much with promises and I am still confused a bit about them, so I guess that I probably have constructed the promise chain wrongly, maybe placed the catch block(s) in the wrong position(s)?
I had a look at common mistakes done with Promises and I certainly had what is called a "Promise Hell".
So, I am not 100% sure yet, but I guess, the result of this was that there was no catch block for my .then("Promise") so the rejection went to nowhere.
I have now changed the calling method to this, and I also removed the try/catch blocks, as it is unnecessary:
public getConversations(teamName: string, channelName: string, messageLimitTopics: number = 0, messageLimitResponses: number = 0) {
return new Promise<any>(async (resolve, reject) => {
let teamId: string = null;
let channelId: string = null;
this.getTeamGroupIdByName(teamName)
.then(tId => {
teamId = tId;
return this.getTeamChannelIdByName(teamId,channelName);
})
.then(cId => {
channelId = cId;
return this.getChannelTopicMessages(teamId, channelId, messageLimitTopics);
})
.catch(error => {
reject(error);
});
});
}
How do i iterate through a list and make sequential network calls using a sdk?
I am trying to use Coinbase's Node sdk and get the first 10 transactions for all accounts.
I have a list of accounts, and i iterating through them and calling client.account, client.transactions, and client.transactions(pagination). And im adding the results to an array of transactions and returning that array.
I couldn't get this to work with async/await or request-promises.
Any ideas?
https://developers.coinbase.com/api/v2#transactions
var rp = require('request-promise');
var coinbase = require('coinbase');
var client = new coinbase.Client({ 'apiKey': 'keyStuff', 'apiSecret': 'secretStuff' });
var accountList = ['acct1','acct2','acct3',];
var transactionList = [];
try {
let ps = [];
accountList.forEach(acctId => {
var account = client.getAccount(accountId, null);
ps.push(rp(account));
});
Promise.all(ps)
.then(responses => {
for (var i = 0; i < responses.length; i++) {
var result = responses[i];
rp(result.account.getTransactions(null))
.then(res => {
res.pagination = 10;
return rp(result.account.getTransactions(null, res.pagination));
}).catch(err => console.log(err))
.then(txns => {
try {
if (txns.length > 0) {
txns.forEach(function(txn) {
var transaction = {
"trade_type": "",
"price": ""
};
transaction.trade_type = txn.type;
transaction.price = txn.native_amount.amount;
transactionList.push(transaction);
});
}
}
catch (err) {
console.log(err);
}
});
}
}).catch(err => console.log(err));
return transactionList;
//-------------------------------------------------------------------
// if (accountList.length > 0) {
// for (let acctId of accountList) {
// console.log("account id: " + acctId);
// await delayTransactions(acctId);
// }
// console.log("got here last");
// return transactionList;
// }
}
catch (error) {
console.log(error);
}
The commented-out delay method has nested async calls like this:
await client.getAccount(accountId, async function(err, account) {
if (err) {
console.log(err);
}
else {
await account.getTransactions(null, async function(err, txns, pagination) {
.
.
.
Solved it by using async/await and promises. awaiting the coinbase methods wouldn't work because they aren't async functions (surprise!).
function delayedMethod() {
new Promise(resolve => {
client.getAccount(accountId, async function(err, account) {
if (err) {
console.log(err);
}
else {
account.getTransactions(null, async function(err, txns, pagination) {
.
.
.
resolve();
});
}
I asked a question two days ago with a reply "Your method must return a Promise (or an Observable)."
I have altered my code to be exactly the same as the example at "https://firebase.google.com/docs/firestore/manage-data/transactions" but the problem is it passes the result as a console log but I need to wait for the write to complete at I need the result.
orderIndex() {
let db = this.firebase.firestore();
var sfDocRef = db.collection("cities").doc("SF");
db.runTransaction(function (transaction) {
return transaction.get(sfDocRef).then(function (sfDoc) {
if (!sfDoc.exists) {
throw "Document does not exist!";
}
var newPopulation = sfDoc.data().population + 1;
if (newPopulation <= 1000000) {
transaction.update(sfDocRef, { population: newPopulation });
return newPopulation;
} else {
return Promise.reject("Sorry! Population is too big.");
}
});
}).then(function (newPopulation) {
console.log("Population increased to ", newPopulation);
}).catch(function (err) {
// This will be an "population is too big" error.
console.error(err);
});
}
I have spent a further two days trying to get a promise returned.
I have seen so many questions asking for help and receiving code suggestions in reply. Please help because I am new to this and have spent over four days on this problem.
By the way the code from firebase.google has an error in
return Promise.reject("Sorry! Population is too big.");
Error: "[ts] Property 'reject' does not exist on type '(resolver: (resolve: (val: IWhenable) => void, reject: (reason: any) => void, notify: (prog...'."
My previous question was at "How do I alter the promises in my function to stop it returning before the data arrives?"
Your function is not returning the promise and also in the then case you are not returning any value.
Try this:
orderIndex() {
let db = this.firebase.firestore();
var sfDocRef = db.collection("cities").doc("SF");
return db.runTransaction(function (transaction) { //Return here
return transaction.get(sfDocRef).then(function (sfDoc) {
if (!sfDoc.exists) {
throw "Document does not exist!";
}
var newPopulation = sfDoc.data().population + 1;
if (newPopulation <= 1000000) {
transaction.update(sfDocRef, { population: newPopulation });
return newPopulation;
} else {
return Promise.reject("Sorry! Population is too big.");
}
});
}).then(function (newPopulation) {
console.log("Population increased to ", newPopulation);
return newPopulation; //Return the value
}).catch(function (err) {
// This will be an "population is too big" error.
console.error(err);
});
}
The following code updates the index, stores it back in firestore and returns the new number.
createOrderNo() {
const getDbIndex = new Promise(
(resolve, reject) => {
if (!this.orderLive) {
this.orderLive = true;
const sfDocRef = this.db.collection('eOrderIndex').doc('orderIndex');
sfDocRef.get().
then(function (sfDoc) {
if (!sfDoc.exists) {
throw "Document does not exist!";
}
console.log('sfDoc.data()', sfDoc.data()['index'])
let index = sfDoc.data()['index'] + 1;
sfDocRef.update({ index: index });
resolve(index);
})
} else {
const reason = new Error('Already live');
reject(reason);
}
})
async function show(index) {
return new Promise(
(resolve, reject) => {
var message = 'New index ' + index;
resolve(message);
}
);
};
// call the promise
async function runPromise() {
try {
console.log('Before get');
let index = await getDbIndex;
let message = await show(index);
console.log(message);
console.log('After get');
}
catch (error) {
console.log(error.message);
}
}
(async () => {
await runPromise();
})();
}
Many thanks to Jecelyn Yeen at scotch.io
I am using Ionic2/Typescript.
I have 2 Promise's that I want to have complete, before I continue (i.e synchronous). So I put the call to the 2 functions in a Promise.all(...), expecting them to complete before resolve is called.
I have the following code:
public openDatabase(): Promise<Array<Message>> {
let promise: Promise<Array<Message>> = new Promise<Array<Message>>(resolve => {
if (false && this.database && this.database != null) {
Promise.all([this.refreshChats(this.database), this.refreshMessages(this.database)]).then(() => {
console.log('openDatabase1: resolve');
resolve(this.messages);
});
} else {
this.database = new SQLite();
this.database.openDatabase({
name: "data.db",
location: "default"
}).then(() => {
Promise.all([this.refreshChats(this.database), this.refreshMessages(this.database)]).then(() => {
console.log('openDatabase2: resolve');
resolve(this.messages);
});
}, (error) => {
console.log("OPEN ERROR: ", error);
});
}
});
return promise;
}
public refreshChats(db: any): Promise<any> {
console.log('refreshChats ');
return db.executeSql("SELECT * FROM chats", [])
.then((chatData) => {
let promises: Array<any> = [];
this.chats = [];
if (chatData.rows.length > 0) {
for (var i = 0; i < chatData.rows.length; i++) {
promises.push(this.populateChat(db, chatData.rows.item(i)));
}
}
Promise.all(promises).then(() => {
console.log('refreshChats return this.chats.length = ' + this.chats.length);
return this.chats;
});
})
.catch(error => {
console.log("ERROR REFRESHING CHATS: " + JSON.stringify(error));
console.log(error);
});
}
From my console output, you can see that resolve is called before the chats are finished refreshing:
refreshChats
populateChat Object {...}
openDatabase2: resolve
refreshChats return this.chats.length = 1
Any advise on how I should structure my Promises is appreciated.
You need to return new promises from inner promise callbacks:
public openDatabase(): Promise<Array<Message>> {
let promise: Promise<Array<Message>> = new Promise<Array<Message>>(resolve => {
if (false && this.database && this.database != null) {
return Promise.all([this.refreshChats(this.database), this.refreshMessages(this.database)]).then(() => {
console.log('openDatabase1: resolve');
resolve(this.messages);
});
} else {
this.database = new SQLite();
return this.database.openDatabase({
name: "data.db",
location: "default"
}).then(() => {
return Promise.all([this.refreshChats(this.database), this.refreshMessages(this.database)]).then(() => {
console.log('openDatabase2: resolve');
resolve(this.messages);
});
}, (error) => {
console.log("OPEN ERROR: ", error);
});
}
});
return promise;
}
public refreshChats(db: any): Promise<any> {
console.log('refreshChats ');
return db.executeSql("SELECT * FROM chats", [])
.then((chatData) => {
let promises: Array<any> = [];
this.chats = [];
if (chatData.rows.length > 0) {
for (var i = 0; i < chatData.rows.length; i++) {
promises.push(this.populateChat(db, chatData.rows.item(i)));
}
}
return Promise.all(promises).then(() => {
console.log('refreshChats return this.chats.length = ' + this.chats.length);
return this.chats;
});
})
.catch(error => {
console.log("ERROR REFRESHING CHATS: " + JSON.stringify(error));
console.log(error);
});
}
Note, return Promise.all and return this.database.openDatabase.