The power of promises for file downloading
In this blog post I will be implementing a file download with a progress indicator using cookies, AngularJS and the promises.
Promises are a powerful concept with a number of advantages, in the following implementation pay attention to these points (your more then welcome to comment):
- Clarity and readability of code
- Error handling
- Separation of concerns
I thought of showing the same implementation without promises, but I think anyone who has tried to handle more than one callback and handle the error cases properly will easily see the difference.
A download button that changes it’s text with set intervals.
At the end it should be in a success state or an error state.
To complicate things a little and show the power of promises I added another step called “validateBeforeDownload”, this step will call the server to validate the download and fail it if necessary.
Downloading a file
The standard way of downloading a file is with a simple “a” tag with an href.
In order to do be able to add the “validateBeforeDownload” step and avoid passing “dom” to a service – I am using an Iframe which a service creates and destroys. This will trigger the download and if the server headers are appropriate the download will begin.
Adding in the progress
Easier said then done! Downloading a file can’t be done with an simple ajax call, so you can’t tell when the download is complete.
The solution I’m using is setting a cookie, let’s call it “download_file” with a timer that checks for a cookie every 500ms.
- While the cookie exists the loading state is preserved.
- Once the request completes, the server deletes the cookie and the timer is stopped.
This isn’t the best solution but is simple and doesn’t require sockets or external plugins.
Just to get the full stack of implementation here is the code for handling the response data and the clearing of the cookie.
Wrapping everything together with promises
Pay attention to the comments in the code, some of the code is there to simulate the server requests and response and are only there for the full picture.
Each visual state of the button is determined by it’s text (scope.downloadExcelText).
Notice $timeout mocks an asynchronous call and it’s response to a server.
this would normally be done with $http.
This is were our hard work pays off and promises start to shine.
Lets step into the promise mechanism –
Prepending the “downloadService.validateBeforeDownload” to the “downloadService.downloadExcel” with the “then” method creates a third promise which shares callbacks for: success, failure and notifications (for the progress).
There is also a finally callback attached to this promise that we use for sharing code between the success and failure.
But the really nice thing here is it also enables handling errors just from the “validateBeforeDownload”, and bubbling them up if needed with $q.reject or by simply throwing the error.
Pay attention that each step towards completion of the promise seems to be handled in an async manner and the actual asynchronicity is handled by the promise mechanism and the service. Magic!