yuяi yuяi - 11 months ago 51
Javascript Question

Ember Auto-save - queueing save requests

For example, a user keeps hitting a key when inside a textbox, and a save request is sent to the server to save the value.

Postponing consequent events by debouncing doesn't work because each keyUp is run in a new Run loop, unlike holding the key down a long time and entering characters that way.

So the question is: what's the most Ember Way approach to A) wait for a request to complete before sending a new one, to ensure an older value doesn't overwrite a newer value, and B) ensure that at least the very last request is sent, saving the most current user-entered value.


After poking around for a bit, seems like one important thing that I missed initially, which caused my saves to run each time a key was pressed, as opposed to once per debounce period, was that the function passed into the debounce method has to be named. Factoring out the save code and passing the reference to the function did the trick of saving a maximum of one times in a 2 second interval.

keyUp: function(e) {
Ember.run.debounce(this, this.saveFieldValue, 2000);

saveFieldValue: function() {
const field = this.get('field');

UPDATE2: @nem035's answer below addresses my other requirement: queueing the saves, so nothing is lost. Debouncing works but doesn't guarantee that.

Answer Source

If you don't care about events being ignored, your approach of debouncing the event is the most Ember way of doing this and is probably the best approach for you but here are possible some alternatives:

Simple approach (extra events get ignored)

A simple thing you can do is just have a flag, initialized to false, to set to true right before the operation starts and reset back to false when the operation finishes.

saveFieldValue: function() {
  if (!this.get('isSaving')) {

    const field = this.get('field');

    this.set('isSaving', true);
    field.save().then(() => {
      this.set('isSaving', false);

This is similar to your debouncing approach (extra events get ignored) but instead we have a flag that prevents an operation from happening if a previous operation hasn't finished. Having a flag can also help you show spinners while data is being saved or disable buttons that can't be used during the save or something similar.

Complex approach (extra events get queued up)

Another thing you can do, if you don't want any of the events to be swallowed/ignored while data is being saved and instead want all of them to happen (eventually), is to create your own event queue to handle this by queueing up the save events and running them in sequence, one after the other.

Additionally you have to handle the case where the queue re-checks itself after some period to run any tasks that might have been queued up in the meantime.

Here's a simple demo using an array as the queue:

// ...

queue: null,

init() {

  // ...

  // initialize a new queue for each instance
  this.set('queue', Ember.A()); 

  // start the queue process

processQueue() {
  const queue = this.get('queue');

  // if the queue is empty, try again in 100ms (this value is arbitrary, see what would work best for you)
  if (Ember.isEmpty(queue)) {
    Ember.run.later(this, this.processQueue, 100);

  // otherwise remove the oldest field
  // save it
  // and then re-run the queue process
  else {
    // remove and save the oldest-first
      .then(() => {
        // here we can do success handling for the saved field
      }, () => {
        // here we can do error handling for the saved field
      .then(() => {

saveFieldValue: function() {
  const {
    field, queue
  } = this.getProperties('field', 'queue');

  // push the current field value to the queue 

Here's an EmberTwiddle example