ember-concurrency icon indicating copy to clipboard operation
ember-concurrency copied to clipboard

Polling a model with ember-concurrency

Open curtu opened this issue 6 years ago • 4 comments

hi! i want to make a request to update my vehicles list every 10 seconds to have updated values. As i read on this post(under fig 4) this can be done with ember-concurrency from a route model hook. I just want to simulate this behavior:

For pages where the data is likely to change a lot, like our filters interface, we store two task instances in our model, one for the current data request, and one for the last performed data request for this same model. With these two task instances, we can display stored data while we update the model with new data, rather than always showing an obstructive loading indicator.

My route looks like:

import Ember from 'ember'
import { task } from 'ember-concurrency'

export default Ember.Route.extend({
    model() {
        return {
            vehicles: this.get('findVehiclesTask').perform(), // TODO: refresh request every 10 sec
            groups: this.get('findGroupsTask').perform(),
            geofences: this.get('findGeofencesTask').perform(),
        }
    },

    findVehiclesTask: task(function* () {
        return yield this.store.findAll('vehicle')
    }),

    findGroupsTask: task(function* () {
        return yield this.store.findAll('group')
    }),

    findGeofencesTask: task(function* () {
        return yield this.store.findAll('geofence')
    }),
});

Any suggestion about how to do that? Thank you!

curtu avatar Sep 12 '18 09:09 curtu

findVehiclesTask: task(function * () {
  while (true) {
    yield timeout(POLLING_TIMEOUT);
    return this.store.findAll('vehicle');
  }
}).cancelOn('deactivate')

jherdman avatar Nov 08 '18 18:11 jherdman

There is a difference between a Task and TaskInstance. task is returning a Task, and when you run perform() on a task it returns a TaskInstance, so to get the last successful task, you can do this.myTask.lastSuccessful which you can use for the last result.

model() {
  return { 
    findVehichlesTask: this.findVehiclesTask
    ...
  };
}
{{ model.findVehichlesTask.lastSuccessful.value }}

mupkoo avatar Jan 04 '19 08:01 mupkoo

@mupkoo I have the same task in regards to polling a model but using findRecord instead of findAll with a dynamic id. In my case, I will have to call perform to pass in the id but in doing so I would lose the ability to continue to poll for more data because I would be returning a TaskInstance instead of a Task. How would I go about doing it with a dynamic id?

model(params) {
  return { 
    checkIfResultFinishedTask: this.checkIfResultFinished.perform(params.result_id)
    ...
  };
}

checkIfResultFinished: task(function * (resultId) {
  while (true) {
    yield timeout(POLLING_TIMEOUT);
    return this.store.findRecord('result', resultId);
  }
}).cancelOn('deactivate')
{{ model.checkIfResultFinished.lastSuccessful.value }}

Would it work if I did the polling in the controller instead using a computed prop? I also want to be able to cancel the task if transitioning and if a certain condition is met.

Result-Controller.js

import Controller from '@ember/controller';
import { task } from "ember-concurrency"
import { computed } from "@ember/object"

export default Controller.extend({

 fetchResult: computed("model.resultId", function() {
    return this.get("fetchResultTask").peform(this.get("model").resultId)
 })
  
 fetchResultTask: task(function*(resultId){
     const result = this.get("store").peekRecord("result", resultId)

    if(result && result.status === "COMPLETED") {
     return result
    }
   
   yield timeout(1000);
   return this.get("store").findRecord("result", resultId)
  })
});

result.hbs

{{ fetchResult.lastSuccessful.value }}

LuisAverhoff avatar Jul 28 '19 14:07 LuisAverhoff

@LuisAverhoff i have made the polling to start from route on setupcontroller hook and the first one request in model

model: function (params) {
	return Ember.RSVP.hash({
		groups: this.store.findAll("group"),
		vehicles: this.store.findAll("vehicle"),
		selectedVehicle: this.store.findRecord("vehicle", params.vehicle_id),
		routes: this.store.query("route", {
			$where: {
				vehicle_id: params.vehicle_id,
				'>=': {
					start_time: params.routeStartDate
				},
				'<=': {
					start_time: params.routeEndDate
				}
			}
		}),
		stops: stops,
		geofences: this.store.findAll('geofence')
	})
},

setupController() {
	this._super(...arguments)
	this.get('pollServerForChanges').perform()
},

pollServerForChanges: task(function* () {
	// eslint-disable-next-line no-constant-condition
	while (true) {
		yield timeout(10000)
		yield this.store.query('vehicle', { geolocation: true })
	}
}).cancelOn('deactivate').restartable(),

In one component i start the polling directly on init hook, and i have two different tasks:

init() {
	this._super(...arguments)

	this.get('pollData').perform()
},

getData: task(function* () {
	let api = this.get('api')

	return yield api.request('/dashboard/home/vehicle-status', {
		contentType: 'application/json; charset=utf-8',
		dataType: 'json',
	}).then(res => {
		if (res.data) {
			return this.formatData(res.data)
		}
	})
}).drop(),

pollData: task(function* () {
	while (true) {
		yield this.get('getData').perform()
		yield timeout(10000)
	}
}).drop(),

curtu avatar Oct 24 '19 09:10 curtu