DVG
DVG

Reputation: 17480

Waiting for Nested Promises to complete

I have a complicated object graph I'm building up in an Ember Controller.

export default Container({
  username: DS.attr('string'),
  items: DS.hasMany('item')
})

export default SomeDetail({
  foo: DS.attr('string')
})

export default Item({
  detail_type: DS.attr('string'),
  detail_id: DS.attr('number'),
  container: DS.belongsTo('container')
})

So, to set all this up, I'm basically trying to

  1. Create the conatainer,
  2. Then, create the details, of which there may be many
  3. Then, create the items, of which there will be as many of the details
  4. Wait for all promises to resolve
  5. Fire off a custom rest action to "activate" the container once it has all of it's stuff.

The code looks like this (coffee), simplified but I think the gist is there

promises = []
store = @store
items = @get('itemsInMyController')
store.createRecord('container',
  username: @get('username')
).save().then(container) ->
  items.forEach (item) ->
    store.createRecord('detail',
      # Set Properties
    ).save().then (detail) ->
      item = store.createRecord('item',
        # Set Properties
      )
      promsies.push item
      item.save()

Ember.RSVP.allSettled(promsies).then (responses) ->
  # Perform Activate Action

When all the promises resolve, everything is how I want it, however, allSettled is firing way too soon, because it's reached before the details have resolved, so the items haven't been created, so there's nothing in the array. This also happens if I add the details to the array, because it's still reached well before the items have been created.

The only thing I can thing of is to have separate arrays tracking the different promises, and having a nested allSettled as each one resolves, but this is starting to feel pretty hairy, and I'm wondering if there's a better way.

Thanks!

Upvotes: 0

Views: 702

Answers (1)

Bergi
Bergi

Reputation: 664307

You need to return promises from your then callbacks so that you can properly unnest them. So first return the item promise from that callback and get a promise that you can actually push to your promises array immediately in that loop:

promises = []
@get('itemsInMyController').forEach (item) =>
  promise = @get('store').createRecord('detail',
    # Set Properties
  ).save().then (detail) =>
    item = @get('store').createRecord('item',
      # Set Properties
    )
    item.save() # this returns a promise
  ) # and `promise` resolves with that result eventually
  promises.push promise

Now you got an array of promises that you can actually pass to allSettled. You must not call that outside of the then callback for the container as well (because promises would still be empty by then), but inside the callback, and you can again return that promise for the array so that you flatten your chain.

And I'd recommend not to use forEeach and manually build up that array, just use map:

@store.createRecord('container',
  username: @get('username')
).save().then (container) =>
  promises = @get('itemsInMyController').map (item) =>
    @get('store').createRecord('detail',
      # Set Properties
    ).save().then (detail) =>
      @get('store').createRecord('item',
        # Set Properties
      ).save()
  Ember.RSVP.allSettled promises
.then (responses) ->
  # Perform Activate Action

Upvotes: 3

Related Questions