4

Summary:

We run into the following situation with our Ember app and are struggling to find an appropriate solution.

We are dealing with a lot of data and hence some of our requests for data are rather slow.

Initially we used Em.RSVP.hash to "bundle" our requests in the model hook of each route. However this would lock up the UI and was ultimately not acceptable.

Our solution was to cascade the requests in the setupController hook like so:

setupController: (controller, model)->
    slowRequest1WhichReturnsAPromise().then (data)->
        # Do something with request 1 data

        slowRequest2WhichReturnsAPromise().then (data)->
            # Do something with request 2 data

            slowRequest3WhichReturnsAPromise().then (data)->
                # Do something with request 3 data

This works. We get instant page load and can show the most relevant data first.

We try to keep Ember up to date (1.11.3 at the time of writing). We are not using Ember Data.

The Problem:

The problem is that the UI is unlocked. The user can, for example, logout (and be redirected to the login page). The requests persist and eventually complete, messing up the csrf token which we set on login and logout. The next time the user tries to login they get a 422 error.

An even simpler scenario is changing a filter setting before the long running requests finish. New requests (often reduced in scope by the filter change) finish before the initial requests. Data then gets overwritten by the eventually finishing original requests.

Our immediate solution was to try and kill the pending promise but cannot find a "modern" way to do it.

This works:

allPromises: []

setupController: (controller, model)->
    deferredRequest1 = new Em.RSVP.defer()

    # Store all promises
    @get('allPromises').pushObject(deferredRequest1)

    # Fire off request 1
    # Spark up the next stage when request 1 finishes
    deferredRequest1.promise.then ->
        deferredRequest2 = new Em.RSVP.defer()
        @get('allPromises').pushObject(deferredRequest2)

# ... 
actions:
    signOut: ->
        @get('allPromises').forEach (promise)->
            promise.reject('canceled')

The above is a bit ugly and simplified to get the point accross.

Ember Docs state that "New code should use the RSVP.Promise constructor instead (of defer)". However there seems to be no way to do this using the new way.

Questions:

Is there a modern way to cancel pending promises / requests?
Is there a better way to split up our page load to enable a clean way to cancel pending requests?

Update

I ended up using the defer. It's not beautiful, but will do till we refactor the UI and perhaps rethink the need for the slow queries.

Thank You.

  • 1
    Yes, there are some modern promise libraries that support proper cancellation. However, `RSVP` doesn't seem to be one of them. – Bergi May 13 '15 at 23:09

1 Answers1

0

First of all, in your "working" solution, you are not canceling the slow queries during signOut. You are just rejecting the promise to terminate the chain of promises, so that no more requests will be sent. (When the last query before signOut returns it can still mutate your cookies.)

If this is already acceptable to you, then what you need to do is to insert signOut-aware checks into the promise chain so that it will be terminated right away if user has signed out.

didSignOut: false

setupController: (controller, model) ->
    slowRequest1WhichReturnsAPromise().then ->
        return null if didSignOut
        slowRequest2WhichReturnsAPromise().then ->
            return null if didSignOut
            slowRequest3WhichReturnsAPromise().then ->
                return null if didSignOut
                // ...

actions: signOut: -> @set('didSignOut', true)

The return null can be replaced by whatever you want to do next. If you want a centralized way to handle this event, you can still reject the promise via the second parameter to .then() and setup a RSVP.on("error", ...) handler for it.

Alan Tam
  • 2,027
  • 1
  • 20
  • 33