Canceling promises with Generators in ES6 Javascript
- Published on
In my previous blog post about I explained the basics about generators in ES6 Javascript. If you haven't read you can check it out here 👉Understanding generators in ES6 Javacsript
Many of you asked for a real-life use case of generators so I'm going to show one of the problems that I have encountered.
Introduction
But in order to explain the problem I have to tell a few words about our product Mews Navigator that we're working on.
The Navigator allows you to check-in online, securely storing your credit card details and giving you full control over the information that you want to share.
So now, imagine that you are doing the online check-in via the app and you are proceeding to the payment step.
So once you click on the next button, you would see a loader and then a list of your payment cards, quite straight forward, right?
Rendering the payment route
Actually, it's a bit more complex under the hood. There are a few steps that need to be resolved before the component is rendered.
// Let's say user goes to this url:
// www.mews.li/navigator/check-in/payment/:reservationId
// 1. This will check if the user is signed in.
// If yes go render <Dashboard /> if not go to <SignIn />
authAction() // async
// 2. We need to fetch the reservations
fetchReservations() // async
// 3. We need to check if `reservationId` and
// route itself is valid (If all checks pass go to next one)
isReservationIdValid({ reservations, currentReservation }) // sync
// 4. Fetch paymentcards
fetchPaymentCards() // async
// 5. Fetching hotel entitites
fetchHotels() // async
// 6. Some of hotels uses PCI proxy vault, if it does,
// we need to initialize PCI proxy script.
doesHotelUsePciProxy({ hotels, hotelId }) // sync
// 7. Fetch and init the script
initPciProxy() // async
We have a few checks and some APIs fetching before rendering the component.
The catch is that if any of these checks could fail and based on which checks to fail, we would redirect to a specific case.
So how to solve this without using any external libraries? Remember last time, when I showed you this example?
function* avengersGenerator() {
yield 'Hulk' // Pausing the execution
yield 'Thor'
yield 'Iron man'
return 'Ultron' // Exiting of finishing the generator
yield 'Spiderman'
}
const iterator = avengersGenerator()
iterator.next()
View source code in codesandbox
Have a look at the return
statement. This would stop the execution and ignore everything that is after the return
statement.
This could give us the possibility to iterate over promises and cancel anywhere in a promise chain.
Proof of concept
Let's create something that is general enough for our use case to solve this case in the routing. The main points were:
- Able to deal with sync and async functions (API calls)
- The code returned redirect as soon as some of the checks fail.
- General enough so we can reuse for other routes as well.
So I opened code sandbox and I come up with this solution 👉 Codesandbox
As you can see in the console, we have multiple actions and some checks. We can move around the check that is supposed to fail and the rest of the code is not executing.
And here is the example of implementation for the payment step route in the code.
function* paymentRouteGenerator() {
yield authAction()
yield fetchReservations()
yield isReservationIdValid()
yield fetchPaymentCards()
yield fetchHotels()
yield doesHotelUsePciProxy({ hotelId })
yield initPciProxy()
}
const CHECK_IN_PAYMENT_ROUTE = {
name: Route.CheckInPayment,
path: '/:reservationId',
action: resolveAction(
generatorWrapper(paymentRouteGenerator),
renderComponent(() => <CheckInPaymentStep />)
),
}
I had to write a handler for our generator. This is a place where the magic
happens. I have explained every step below in the comments.
const generatorWrapper = (generator) => (context) => {
// 1. Creating an iterator
const iterator = generator(context)
// 3. This function except yielded as a argument
const handle = (yielded) => {
const handleWithRedirectCheck = (route) => {
// 4. Here is where the magic happens, we check if there is a redirect, if yes,
// it would redirect (cancel) and will not execute the rest of the generator
if (get('redirect', route)) {
return route
}
// Otherwise continue
return handle(iterator.next())
}
// Exit if we are at the end of the generator
if (yielded.done) {
return
}
// Handling the async case if action/check is a promise
if (isPromise(yielded.value)) {
return yielded.value.then(handleWithRedirectCheck)
}
// If its not a promise, we can call handleWithRedirectCheck directly
return handleWithRedirectCheck(yielded.value)
}
// 2. Handling the iterator
return handle(iterator.next())
}
View the source code in codesandbox
For now, I'm just playing with it, so if you have any idea how to solve this in a nicer way, definitely let me know. 😉
Thanks for reading
Let me know in the comments section how you feel about this generators series. If you love it, you know what to do! Share it with your friends and colleagues.
If you want me to cover some topics in the next post, DM me on here on dev.to or on twitter @tuanphung_, or if you have any suggestions, feel free to comment below.
See ya next time and keep on hacking ✌