I think you might find something like this introduction to Promises help wrap your head around the fundamental concepts. You are going to have to completely trashcan your mental model of “yo service, give me a category”. Asynchronous programming doesn’t work that way. You have to say “once you’ve got that category, here’s what I want to do”.
So you want something like this:
getCategory(): Promise<Category[]> {
// note: I recommend never ever using 'SELECT *'
// because it has a tendency to silently start failing
// in mysterious ways when you add columns later
return this.storage.query('SELECT name, type, note from category')
.then((data) => {
let ncategories = data.res.rows.length;
let rv:Category[] = new Array(ncategories);
for (let i = 0; i < ncategories; i++) {
let item:any = data.res.rows(i);
rv[i] = {name: item.name, type: item.type, note: item.note};
}
return rv;
});
}
Thank you for the clarification. And yes, I’ve been trying to wrap my head around promises for a while, started from the article you pointed. Really useful.
I stand behind my previous suggestions. IMHO, your getCategory is an example of the explicit Promise constructor antipattern, and you are foisting unnecessary work onto the page constructor. You are fighting against the Promise concept, and I think anybody else struggling with a similar issue should ignore your “solution”.
Still learning this promise thing, so forgive me for bad patterns.
You say [quote=“rapropos, post:5, topic:52000”]
and you are foisting unnecessary work onto the page constructor.
[/quote]
The only work I know to be doing in the constructor is recreating the array with this.
this.categories = Array.from(category);
The rest of the work is in templates, the ngFor.
Instead of that, I could do this in the service promise:
resolve(Array.from(data.res.rows))
which would send the ‘unnecessary’ workload into the promise.
This is way too simplified for an example to fully express the powers of Promise, so yeah, in an elaborate example, pushing the workload to constructor might be a bad practice. In this case, I just need the objects returned from the db. It is just a select. Had to use promise because the query on storage returns a promise. Otherwise, a ‘hit db and run’ was what I was thinking.
Right. That’s not the constructor’s job. When your synchronous brain wants to declare a function returning a Foo, and it is coming from an asynchronous source, be it storage or a network request or whatever, make that function return a Promise<Foo> (or Observable<Foo>). Don’t just return whatever the upstream source gives you and make callers unpack it. Every single caller will have to have that same unpack code, which is unnecessary repetition.
myservice.getCategory()
.then((category) =>
Anybody reading this code would expect category to be a Category. It isn’t. It’s a Promise<any>. It should actually be named categories, and it should be a Category[]. I also think Array.from is analogous to SELECT *. If Category objects are known to have certain properties, they should all be explicitly assigned in getCategories(). Otherwise, you set yourself up for a situation where you add a field to Category, forget to put it in the SQL schema, and can’t see anywhere in your code where it failed to be assigned.
Your comment recreate new array from old category array doesn’t describe what is happening. There is no ‘old category array’ being used. There is a Promise resolving to another Promise, which should not ever happen.
I’m sorry if any of this sounds overly harsh, but if you’re marking things as solutions and suggesting that future readers might benefit from them, I think it’s in all of our best interests to make them as good as we can.
Is there any necessity of using platform.ready in the code you demonstrated or would that be unnecessary as that promise already accounts for when the data is ready?
Yeah, but I’m not entirely sure how exactly to wrap the promise in there? All my SQL queries are handled by a data model I’m using and I’ve made it so that all of them - but this one, now - are wrapped in platform.ready.
refreshFromSQL() {
return this.itemsSQL.query("SELECT * FROM data").then((data) => {
let len = data.res.rows.length;
let itemStorage = new Array(len);
for (let i = 0; i < len; i++) {
itemStorage[i] = {name: data.res.rows.item(i).name, systemid: data.res.rows.item(i).systemid};
}
return itemStorage;
});
}
Yes on platform.ready. There are a number of ways you could enforce it. Probably the simplest would be to wrap the construction of the database object in the service constructor like so: