Mapping template helpers

I was wondering if there are good ideas to solve the following problem:

  • I have a data-loading helper which takes some input and returns a promise
  • Now on a different page I would like to load an arbitrary amount of items in parallel

Here a pseudo-example of what I would like to accomplish:

{{#let 
  (join-promises (array "John" "Anne") (load-items-by-name))
  as |allPromise|
}}
  {{! ... do something with the promise }}
{{/let}}
// app/helpers/load-items-by-name.js
import { helper } from '@ember/component/helper';

export default helper(function loadItemsByName([name]) {
  return fetch(`/data/${name}.json`);
});

// app/helpers/join-promises.js
import { helper } from '@ember/component/helper';

export default helper(function joinPromises([array, callback]) {
  return Promise.all(array.map((value) => callback(value));
});

Right now, you can’t pass helpers as reference (I know there is an RFC for this, but it’s not possible right now AFAIK), so the above example can’t work.

Does anybody have ideas or recommendations on how to solve such a thing nicely, without needing to e.g. create a second (load-items-by-many-names) helper or similar?

If you were to use it like in the template above I would something like this:

{{#let 
  (join-promises 
    (array
      (load-items-by-name "John")
      (load-items-by-name "Anne")
    )
  )
  as |allPromise|
}}
  {{! ... do something with the promise }}
{{/let}}

then the join promises helper could be as simple as:

// app/helpers/join-promises.js
import { helper } from '@ember/component/helper';

export default helper(function joinPromises([array]) {
  return Promise.all(array);
});

If you wanted to use some arbitrary array of names you’d need some helper that could map an array by a callback function, so if you wanted to really make it atomic you could have three helpers involved: one that does the callback/fetch, one that maps an array to a given callback helper, one that combines the promises into all

I wrote a little experimental addon kinda like this (I need to update it though :grimacing:) and this is one of the examples in the docs, which I think is pretty similar:

<NeedsAsync @needs={{async-all (array
  (find-record "user" "1")
  (find-record "user" "2")
  (find-record "user" "3")
)}} as |States|>
  <States.loading>
    Loading users...
  </States.loading>

  <States.loaded as |users|>
    {{#each users as |user|}}
      <div>{{user.fullName}}</div>
    {{/each}}
  </States.loaded>
</NeedsAsync>

Yeah, I got to about this state to, which works decently, but the “fetching it for an arbitrary/dynamic array” part is exactly where I got stuck, because as you can’t pass helpers by reference you can’t really use them as callbacks.

Your addon looks nice, though! :+1:

Hmmmm yeah… this is a little less fun but you could keep all the “callback functions” on a service and have a class-based helper that could reach into the service for an arbitrary callback function to use :thinking:

Or maybe you could use something like the ember-composable-helpers approach like the map helper? EDIT: though i guess that gets you back to the same problem of “where does the action live” so I guess a service is the only real answer I can think of.