В моем предыдущем сообщении в блоге я объяснил основы работы с генераторами в ES6 Javascript. Если вы еще не читали, вы можете проверить это здесь 👉 Общие сведения о генераторах в ES6 Javascript

Многие из вас просили о реальных примерах использования генераторов, поэтому я собираюсь показать одну из проблем, с которыми я столкнулся.

Введение

Но чтобы объяснить проблему, я должен сказать несколько слов о нашем продукте Mews Navigator, над которым мы работаем.

Навигатор позволяет вам регистрироваться онлайн, надежно сохраняя данные вашей кредитной карты и предоставляя вам полный контроль над информацией, которой вы хотите поделиться.

А теперь представьте, что вы делаете онлайн-регистрацию через приложение и переходите к этапу оплаты.

Итак, как только вы нажмете на следующую кнопку, вы увидите загрузчик, а затем список ваших платежных карт, довольно прямо, не так ли?

Отображение маршрута платежа

На самом деле, под капотом все немного сложнее. Перед отрисовкой компонента необходимо выполнить несколько шагов.

// 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

У нас есть несколько проверок и выборка некоторых API перед рендерингом компонента.

Загвоздка в том, что если какая-либо из этих проверок может завершиться неудачно, и в зависимости от того, какие проверки не пройдут, мы перенаправим на конкретный случай.

Итак, как решить эту проблему без использования каких-либо внешних библиотек? Помните прошлый раз, когда я показывал вам этот пример?

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();

Взгляните на выражение return. Это остановит выполнение и проигнорирует все, что находится после оператора return.

Это может дать нам возможность перебирать обещания и отменять их в любом месте цепочки обещаний.

Доказательство концепции

Давайте создадим что-то достаточно общее для нашего варианта использования, чтобы решить этот случай при маршрутизации. Основными пунктами были:

  • Возможность работать с функциями синхронизации и асинхронности (вызовы API)
  • Код вернул перенаправление, как только некоторые проверки не прошли.
  • Достаточно общий, поэтому мы можем использовать его и для других маршрутов.

Итак, я открыл песочницу кода и придумал такое решение 👉 Codesandbox

Как вы можете видеть в консоли, у нас есть несколько действий и несколько проверок. Мы можем обойти проверку, которая должна завершиться ошибкой, а остальная часть кода не выполняется.

А вот и пример реализации маршрута шага платежа в коде.

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 />)
  )
};

Пришлось написать обработчик для нашего генератора. Это место, где случается magic. Я объяснил каждый шаг ниже в комментариях.

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());
};

Пока я просто играю с этим, поэтому, если у вас есть идеи, как решить эту проблему лучше, обязательно дайте мне знать. 😉

Спасибо за чтение

Сообщите мне в разделе комментариев, что вы думаете об этой серии генераторов. Если вам это нравится, вы знаете, что делать! Поделись с друзьями и коллегами.

Если вы хотите, чтобы я затронул некоторые темы в следующем посте, напишите мне в DM на dev.to или в твиттере @phung_cz, или если у вас есть какие-либо предложения, не стесняйтесь комментировать ниже.

Увидимся в следующий раз и продолжай взламывать ✌

Посмотрите, что мы создаем @ Mews systems, мы также нанимаем разработчиков и людей на другие должности. Напишите мне, если у вас возникнут вопросы.