Canceling promises with Generators in ES6 Javascript

·

5 min read

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?

image.png Navigator payment step in online check-in

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.

image.png Navigator payment step in online check-in View the source code in codesandbox

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 @phung_cz, or if you have any suggestions, feel free to comment below.

See ya next time and keep on hacking ✌


Have a look on what are we building @ Mews systems we are also hiring devs and people for other positions. DM me if you have any questions.