WinJS Promise Pitfalls
I’ve been using WinJS promises in Windows Store apps for the past couple years. Async programming is much different than the standard synchronous, imperative programming most of us are used to. Here are a few lessons I’ve learned about using WinJS Promises, although these lessons could apply to any implementation of the Common JS Promises/A spec.
Missed Catch
The then() method takes up to 3 parameters – onComplete, onError and onProgress. But what happens if an exception is thrown in the onComplete handler?
Well, as specified in the Common JS Promises/A spec, the return value of then() is a promise in the error state. It does not call the onError handler. To handle an exception thrown by JSON.parse() we need to add an additional then(). The easiest way to do this is to provide a trailing then() that passes null for the onComplete handler.
Not Done Yet
As mentioned before, if an exception is thrown in the onComplete handler of then() the return value is a promise in the error state. But if there is no code to handle this error state then the error will silently go unhandled and your application will just keep running in a potentially invalid state.
The right thing to do is end each promise chain with done() which will re-throw any unhandled errors. If the error is unrecoverable, you can just pass no parameters to the done() method and it will re-throw the exception in the next turn of the event loop, likely forcing your Windows Store app to crash because there is no chance to catch the exception. Otherwise if your application can continue running after encountering the error, its in your best interest to at least log the error in order to make debugging easier. Other good options would be to assert, display an error message to the user and/or try to perform the action again. Like the previous example, the easiest way to do this is to provide a trailing done() that passes null for the onComplete handler.
Indisposed
In asynchronous programming, the state of the world can change right out from underneath you. An easy mistake to make is to tear down and dispose an object but to leave a promise inside it running. For example, let’s assume we want to show a Message each time the user performs an action. Additionally, we want to show just one message at time.
If the user is quick enough, he/she might perform two actions in quick succession:
- User performs Action A
- Start showing Message A
- User performs Action B
- Dispose Message A (we only want to show one message at a time)
- Start showing Message B
If step 4 causes Message A to be disposed before the animation has finished, it will cause a null reference exception when attempting to access this._element.dispatchEvent() (line 17) after the animation promise completes in showAsync() and calls fireEvent().
There are two potential ways to avoid this issue. The first is to store any promises in a member variable and then call cancel() on each one in dispose(). However, it turns out that WinJS animations support cancellation but swallow the cancellation and complete the animation promise, which means our then() following the animation still runs and we still get a null reference exception in the example above.
The alternative is to use a disposed flag inside your class and check the flag before proceeding. In this case, I’m returning the promise to the caller, so I cancel the promise if the class has been disposed the same way the cancel() method works – by returning a promise in the error state with the error message “Canceled”.