Ember-concurrency ajax-throttling example not working in my dev environment

I first asked this question in Stackoverflow, but after two days without a response, i’ve gotten antsy so I am posting the question here as well.

My question is, what do i need to fix so that my implementation of the ember-concurrency Ajax Throttling example works as expected. Specifically, in my implementation no log entries are displayed (as they are on the example page), and the console.log statement that i added in the loopingAjaxTask inner function is not executed nor is the console.log statement in the task. I have copied the example nearly verbatim with the only changes being that i’ve added in those console.log statements.

The line ‘looping Ajax task outer function’ is written to the console 8 times as expected, but the ‘looping Ajax task inner function’ is never written to the console log. The ‘Hello World’ text from the component is written to the page.

I am using ember 4.8 and ember-concurrency 2.3.7.

Here’s the template where i’ve included the AjaxThrottling component.

app/templates/example.hbs

{{page-title "Example"}}
<p>Here's where the log entries should appear</p>
<AjaxThrottling />
{{outlet}} 

app/components/ajax-throttling.hbs

<div>
    hello world
  <ul>
    {{#each this.logs as |log|}}
      <li style={{color log.color}} {{! template-lint-disable no-inline-styles }}>{{log.message}}</li>
    {{/each}}
  </ul>
</div>

and here is component’s js page

import Component from '@glimmer/component';
import { enqueueTask, task, timeout } from 'ember-concurrency';

function loopingAjaxTask(id, color) {
    console.log('looping Ajax task outer function');
    return function* () {
        console.log('looping Ajax task inner function');
        while (true) {
        this.log(color, `Task ${id}: making AJAX request`);
        yield this.ajaxTask.perform();
        this.log(color, `Task ${id}: Done, sleeping.`);
        yield timeout(2000);
      }
    };
  }

export default class AjaxThrottlingComponent extends Component {

    tagName = '';
    logs = [];
  
    ajaxTask = enqueueTask({ maxConcurrency: 3 }, async () => {
      // simulate slow AJAX
      console.log('inside the task')
      await timeout(2000 + 2000 * Math.random());
      return {};
    });
  
    @task({ on: 'init' }) task0 = loopingAjaxTask(0, '#0000FF');
    @task({ on: 'init' }) task1 = loopingAjaxTask(1, '#8A2BE2');
    @task({ on: 'init' }) task2 = loopingAjaxTask(2, '#A52A2A');
    @task({ on: 'init' }) task3 = loopingAjaxTask(3, '#DC143C');
    @task({ on: 'init' }) task4 = loopingAjaxTask(4, '#20B2AA');
    @task({ on: 'init' }) task5 = loopingAjaxTask(5, '#FF1493');
    @task({ on: 'init' }) task6 = loopingAjaxTask(6, '#228B22');
    @task({ on: 'init' }) task7 = loopingAjaxTask(7, '#DAA520');
  
    log(color, message) {
      let logs = this.logs;
      logs.push({ color, message });
      this.set('logs', logs.slice(-7));
    }
}

I think this example in the docs is pretty borked. It looks like it was only half migrated from Ember classic syntax (for example tagName no longer exists on the Glimmer component API). There’s also this issue where it looks like the on('init') functionality was broken. The init hook is also not present on Glimmer components so i’m not sure how that would have worked anyway. More than likely you’d want to kick off the tasks in the constructor or something. Anyway… not sure what all has to change quite yet but I’ll play around with it soon and try to report back.

Thanks for your response and diving into this. I’ll start poking around as well to see what i can come up with. Nice to know that i wasn’t dorking things up ;).

Ok here’s what I found:

  • the biggest problem seems to be that on: 'init' doesn’t seem to work. This prevents the actual tasks (inner function) from ever being performed
  • tagName is extraneous and can be removed
  • this.set doesn’t work, iirc this.set and this.get APIs aren’t available on Glimmer components. Ember.set could still be used but it’s better to convert logs to autotracking and use other methods of updating it

Below is the code I ended up with that seems to work ok. I chose to add another task that initializes the other tasks (in lieu of a working on: 'init' task modifier. There’s probably a better or more elegant way of doing this, I just threw this in quickly to get it working.

import Component from '@glimmer/component';
import { enqueueTask, task, timeout } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';

function loopingAjaxTask(id, color) {
  return function* () {
    while (true) {
      this.log(color, `Task ${id}: making AJAX request`);
      yield this.ajaxTask.perform();
      this.log(color, `Task ${id}: Done, sleeping.`);
      yield timeout(2000);
    }
  };
}

export default class AjaxThrottlingExampleComponent extends Component {
  @tracked logs = [];

  constructor() {
    super(...arguments);
    this.startTasks.perform();
  }

  startTasks = task(async () => {
    // this is just to slightly delay the start of the tasks so it doesn't
    // double-modify this.logs in the initial render pass
    await timeout(50);
    this.task0.perform();
    this.task1.perform();
    this.task2.perform();
    this.task3.perform();
    this.task4.perform();
    this.task5.perform();
    this.task6.perform();
    this.task7.perform();
  });

  ajaxTask = enqueueTask({ maxConcurrency: 3 }, async () => {
    // simulate slow AJAX
    await timeout(2000 + 2000 * Math.random());
    return {};
  });

  @task({ on: 'init' }) task0 = loopingAjaxTask(0, '#0000FF');
  @task({ on: 'init' }) task1 = loopingAjaxTask(1, '#8A2BE2');
  @task({ on: 'init' }) task2 = loopingAjaxTask(2, '#A52A2A');
  @task({ on: 'init' }) task3 = loopingAjaxTask(3, '#DC143C');
  @task({ on: 'init' }) task4 = loopingAjaxTask(4, '#20B2AA');
  @task({ on: 'init' }) task5 = loopingAjaxTask(5, '#FF1493');
  @task({ on: 'init' }) task6 = loopingAjaxTask(6, '#228B22');
  @task({ on: 'init' }) task7 = loopingAjaxTask(7, '#DAA520');

  log(color, message) {
    this.logs = [...this.logs.slice(-6), { color, message }];
  }
}

EDIT: oh i see in the API docs that on applies only to @ember/component that on only applies to ember/component which totally makes sense, just means this example in the docs was even more in need of some love.

EDIT 2: and i guess the imports are hidden in the docs examples so while the code shows that they’re still using @ember/component (which would probably still work great with the code that’s in there) it doesn’t work for glimmer component. So TECHNICALLY the guides are still accurate, but they still rely on @ember/component and don’t seem to indicate that.

1 Like

I got your code working in my startup project. Thank you for your efforts and sharing your knowledge. Both are greatly appreciated by me.

The code i shared came from the github repo. I guess there’s some clean up work to do there as well.