Modelling asynchronous flows using Promises and a use case for deferred
All of us have used promises in one way or the other. More often than not, we consume promises created and exposed by something else (Browser Web Apis being an example), rather than produce promises. When we consume promises, we don’t have a say in (and possibly don’t care about) how or when the promise is resolved, we only care about the fact that once it is resolved, we will get some kind of a signal and we can run our code. The resolution or rejection is always handled by the producer of the promise, we can only listen to the promise status changes, and there is no way for us to change this status explicitly. Usually, this is all we need when consuming promises, we can hook on to the success/error status, and do whatever we need to do. We can, of course, consume the promise and produce a further promise which can have a different status than the consumed promise. As an example, inside the
promise.then() block, when the current promise
resolves with a particular value, we can
throw an error, to
reject the new promise that is created by
.then, and for all practical uses and purposes it would still seem that the result of the original task is rejection. In this case, we implicitly create a promise (the runtime creates a promise for us to return from
then) and decide its status.
On the other hand, when producing promises explicitly, we actually need to handle how and when to
reject the promise. The
reject functions are only available inside the promise executor function, what if we don’t know at the time of creating the promise how that promise is going to be resolved, or who is going to resolve it? This is the use case that we are going to discuss in this post.
Producing promises, is rather rare, because, in most cases, the asynchronous task is already promisified, as in the case of the browser returning a promise for fetch requests.
Nonetheless, following are some examples where we might create promises in our code:
- Promisifying an existing function which uses callbacks.
- Promisifying a long running task, which we would like to execute asynchronously (using
setIntervaland wrapping them in promise)
- Wrapping an existing promise in a new promise to enhance some functionality, for e.g, retrying the existing promise for some number of times or until it succeeds, or when you want to enhance the original promise resolution condition(in most cases, this can be achieved by promise chaining, which implicitly creates a promise)
- Sometimes you may wrap a function which can directly return a value, say
fn1, in a promise, so that the usage of this function
fn1is consistent with another function
fn2that returns a promise, if both of them are alternatives of each other, so that the consumer does not need to care about the fact that
fn1returns a value while
fn2returns a promise.
- To model asynchronous flows in your UI. For example:
- waiting for a user input or action before your code can move forward with the logic flow
- when an action in some cases can return immediately but in other cases, needs to wait to return a value, we can model it using promises which immediately resolves if the value is available and waits if the value is not available immediately (This would require the consumer of this action to be able to handle promises)
In this post, we will look a little deeper into modelling asynchronous flows using promises.
Reiterating the problem statement:
Whenever we create a promise, we can control when/how it is resolved or rejected inside its executor function. What if we want to resolve a promise from outside of the promise executor function? May be keep a reference to the promise and explicitly resolve it when some event happens? Let’s say, you create a promise now, but want to resolve it later on click of a button. Based on the current available syntax, you would probably need to write your button event handler inside the promise executor function, so that it has reference to the
resolve function OR you can keep a reference to the
resolve function outside the executor, and use that later to resolve the promise explicitly. This is where the
deferred object/pattern comes in. It abstracts the task of keeping a reference of the promise in your code explicitly.
Let’s start with a basic, easy to understand flow. We have a hypothetical e-commerce app. On the product details page, there is an option for the user to provide the quantity and place order. You have an Order
button, it places an order
onClick. So you create a
handleClick function and
placeOrder from inside it.
Let’s add some complexity to the mix. Let’s say every time the user wants to
placeOrder, we want to confirm if they are sure. Let’s add some markup to show the confirmation UI . Now all we are going to do is to move the
placeOrder call from the Order
onClick to the Confirmation Yes
onClick, because the order button does not directly decide whether to place an order or not. The Order
button opens the confirmation UI and the confirmation Yes
button makes a call to
Hmm, that was easy. But in real world scenarios, it does not make sense to confirm every time the user wants to place an order. Let us only confirm if the quantity is above a certain threshold.
onClick of the Order
button, based on the quantity entered, we need to decide whether to show the confirmation UI or not. If not, we
placeOrder directly, if yes, then we show the confirmation UI and
placeOrder when the user clicks on Yes.
This is still not a big change, the only minor issue is that we have to call the
placeOrder function at multiple places now. In terms of code readability and responsibility, I would personally like it better if the Order
button is responsible for
placeOrder and the call is only ever inside the
onClick of the
To further my point, consider the scenario where the confirmation button
onClick, needs to pass on the control to some other functions to decide whether to allow the user to place an order or not. For example, let’s say if the quantity is greater than 400, we first show a confirmation dialog, if the user confirms, we call another function which gets the maximum available quantity from the server and only then places the order. That would be a lot of indirection. Or to think of it more generically, let’s see our OrderButton as a component which can be reused across pages, it does not expose the
placeOrder method and so on every page when it is clicked, it just requires a boolean value from the parent which tells it to go ahead with the order or not. So, on every page the condition to move ahead might depend on different things, the OrderButton only cares about the boolean returned from the parent. In this case, the redirection for placeOrder just cannot happen the usual way. Enter promises.
What if the order
button could wait for anything that needs to happen (user confirmation, api call, etc) and then do the
placeOrder call ?
What if the Order
button creates a promise and waits for that promise to be resolved by whoever is responsible for finally figuring out whether the order should be placed or not?
Hold that thought. Let’s try it out.
Oooo, feels good, but does not look good. As we discussed earlier, we only have access to
reject inside the promise executor function, so we had to create our
handleCancel functions inside the promise executor function. Let’s see if we can iron that out.
Woohoo, we could make it better by keeping a reference to the promise
reject functions so that we can call them from outside the executor function. This helps us move the
handleCancel out of the promise executor function.
Can we still do better though?What if we could somehow abstract this functionality of promise creation and keeping reference to the
reject so that we can call them later ?
That is exactly what a
deferred is. It is an object which exposes a promise and orchestrates the promise lifecycle.
We create a
Deferred constructor and use that to create a deferred object inside the
onClick of the Order button. We just abstracted away the promise creation and the task of holding on a reference to
Surely, there are other patterns to solve this problem, specifically in the frameworks that you are using. There is always a possibility to handle this workflow locally, where the Order functionality is strewn through out the code, this solution is a way to encapsulate the order placing functionality inside
onClick of Order button, and to make it more understandable in some cases.
This is a particular use case for using deferred objects. Speaking generically, whenever we produce promises and we don’t know at the time of promise creation, how and when the promise would be resolved, we can make use of the
deferred pattern/object to keep a reference to promise
resolve/reject it later whenever we want.
NOTE: This is just a quick and dirty implementation of the deferred pattern as a proof of concept. Much better and complete implementations are easy to find on the web.