I need help on developing a good strategy for polling in the router. I have a route queries/:query_id/results/:result_id that I transition to whenever the user executes a query. In this I route I need to load two things: the result model that is associated with this route and a table object using the url inside the result model. The problem is, if a query is long running I need to poll and ask the server if the query is finished. Only then can I download the table. I'm using ember concurrency to do all my polling and it works great except for a small edge case. This edge case has to do with the fact that If my polling function gets canceled after it is finished and while it downloads the table, then it will get stuck saying "loading the table" because it only triggers the polling when the status of the query is not completed. The download of the table happens inside the polling function but only when the query is finished. I'm doing all my data loading in the result route so maybe someone can offer some alternatives to do this. I also need to mention that each table will be displayed in a separate tab(bootstrap tabs). Because of this I want to minimize the amount of times I fetch the table(reason why I'm pushing it to the store) when I switch between tabs as each tab is a link to a new result route.
Relevant code in the result route
import Route from "@ember/routing/route";
import { inject as service } from "@ember/service";
import { reject } from "rsvp";
import { task } from "ember-concurrency";
import { encodeGetParams } from "gtweb-webapp-v2/utils";
export default Route.extend({
poller: service("poller"),
fetchResults: task(function*(result_id) {
try {
const result = yield this.store.findRecord("result", result_id);
if (result.status === "COMPLETED") {
const adapter = this.store.adapterFor("application");
const queryString = encodeGetParams({ parseValues: true, max: 25 });
const table = {
table: yield adapter.fetch(
`${result._links.table.href}?` + queryString
)
};
// Cache the table that we fetched so that we dont have to fetch again if we come back to this route.
this.store.push({
data: [
{
id: result_id,
type: "result",
attributes: table,
relationships: {}
}
]
});
return true;
}
} catch (err) {
return reject(err);
}
}),
model(params) {
const result = this.store.peekRecord("result", params.result_id);
if (!result || result.status !== "COMPLETED") {
const poller = this.get("poller");
poller.startTask(this.get("fetchResults"), {
pollingArgs: [params.result_id],
onComplete: () => {
this.refresh(); // If we finish polling or have timeout, refresh the route.
}
});
}
return result;
},
setupController(controller, model) {
const query = { title: this.modelFor("queries.query").get("title") };
controller.set("query", query);
this._super(controller, model);
},
actions: {
willTransition() {
const poller = this.get("poller");
poller.abort(); // Task was canceled because we are moving to a new result route.
}
}
});
Idea
One idea is probably creating a separate route for loading the table
i.e queries/:query_id/results/:result_id/:table_id and only transition to this once the query is completed. From there I can safely load the table. Only problem I have with this is that the result route will simply be involved with loading the result. There will be no components that will be render in the result route; only in the table route.
Ola @Luis thanks for your question
I don't know exactly what you're trying to do with your code but it seems a tiny bit complicated for what you're trying to achieve because you're using ember-data we can simplify this problem significantly.
The first thing that we can rely on is the fact that when you call
peekRecord()orfindRecord()you are actually returning a Record that will be kept up to date when any query with the same ID updates the store.Using this one piece of knowledge we can simplify the polling structure quite significantly. Here is an example Route that I have created:
I have created a
timemodel, adapter, and serializer that queries a public time API which will return the current time for a particular timezone. As you can see, I am storing the initial result and before I return it, then I have set up my standard JavaScript interval withsetInterval()that will poll every 10 seconds to update the time. I then set the interval on the route so I can cancel it when we leave.(Note: I have only set the interval to 10 seconds because I got rate limited by the service for polling every 1 second you can set this to however often you want to check for data)
I have edited my example to also include another way to stop polling based on the result of the query itself in the interval function. This is a little bit of a toy example but you can edit this to fit your needs.
As I said in the beginning, we are relying on the way that ember-data works for this to be seamless. Because
findRecord()andpeekRecord()always point to the same record you don't need to do anything special if a subsequent request to your data includes the results that you need.I hope this helps!