We have A job app that needs to get A list of job ids (request A) after launching, and then build n pages based on the length of the list of ids. When the user turns to a page, the details of the current job are retrieved based on this ID (request B).

This is the basic flow, but the app also has many different modes that request different apis based on the character and the scene. For example, the teacher role, there are homework, preview homework, grading homework mode. Being a student also involves many modes, such as practicing homework, correcting homework, reviewing homework, etc.

The API interfaces and data structures corresponding to different schemas may differ to some extent.

  • In the teacher layout mode, the request is not interface A, but interface A’ (the corresponding interface data parsing logic is also different).
  • In addition to obtaining details of the homework (request B), teachers need to obtain students’ answers to the questions (request C) for correcting and reviewing the homework.
  • Students revise the mode and get the answers to the questions at the same time as request A.

So my idea is to encapsulate these differences in asynchronous logic based on patterns and make them invisible to the outside world. External calls simply return a uniform data format. The result is an asynchronous flow control wheel built with RxJS. What problem does this wheel mainly solve?

  1. Each different block of asynchronous logic can be defined in a declarative way
  2. The dependencies between different asynchronous logical blocks can be sequential or concurrent. And each asynchronous logic has the ability to create child asynchronous logic.
  3. Each asynchronous logic, however it is processed, is finally aggregated into a unified context, and subsequent business logic can simply consume this data.
  4. Hide the complexity of the RXJS operator, which can be used as long as it knows promise

Let’s start with asNYC-flow

The details of async-flow are in the readme document on the project home page. Here is how to use async-flow with this practical example

Since there are many different modes, we can create different asynchronous logic configurations:


import * as apis from "./api";

const getHomeWorkList = {
  name: "getHomeWorkList".flow: (context) = > {
    return apis.getHomeWorkList(context.homeworId);
  },
  map: (result, context) = > {
    // Do some data processing on the job list, such as JsonAPI format conversion, which is omitted here
    const finalResult = doSomeParse(result);
    // Context can define additional attributes to facilitate subsequent asynchronous logic fetching
    context.exerciseId = finalResult[0].id;
    context.list = finalResult;
    returnfinalResult; }};const getPreviewList = {
  name: "getPreviewList".flow: (context) = > {
    return apis.getPreviewList(context.previewIds);
  },
  map: (result, context) = > {
    // We assume that the results obtained in preview mode can be used without parse
    context.exerciseId = result[0].id;
    context.list = result;
    returnresult; }};const getExerciseDetetail = {
  name: "getExerciseDetetail".flow: (context) = > {
    // getExerciseDetetail must be run after getPreviewList or getHomeWorkList
    // So they can get exerciseId injected after they're done
    const exerciseId = context.exerciseId;
    return apis.getExerciseDetetail(exerciseId);
  },
  map: (result, context) = > {
    // Do some data processing on the job list, such as JsonAPI format conversion, which is omitted here
    returnresult; }};const getUserAnswerById = {
  name: "getUserAnswerById".flow: (context) = > {
    return apis.getUserAnswerById(context.exerciseId);
  },
  map: (result, context) = > {
    returnresult; }};const getUserAnswerList = {
  name: "getUserAnswerList".flow: (context) = > {
    return apis.getUserAnswerList(context.homeworId);
  },
  map: (result, context) = > {
    returnresult; }};Copy the code

We start by declaratively defining the specific request logic and data parsing logic for each asynchronous phase.

As shown in the code above, we define getPreviewList for teacher assignment mode and getHomeWorkList for other modes. ExerciseId is also injected into the context in order to unify the subsequent way to get exerciseId.

We also define a uniform getExerciseDetetail that gets the details of the current job. The asynchronous logic uses exerciseId defined by the context, so it doesn’t know which process the exerciseId comes from.

Finally, two interfaces to get the user’s answers are defined.

After defining all the asynchronous processes, all that remains is to assemble them.


import {buildFlow} from '@tomyail/async-flow';

GetPreviewList getPreviewList getExerciseDetetail
const teacherPreviewFlow = (context) = >
  buildFlow(context, [getPreviewList, getExerciseDetetail]);

// get getHomeWorkList and getExerciseDetetail
const studentPractice = (context) = >
  buildFlow(context, [getHomeWorkList, getExerciseDetetail]);

// Get getHomeWorkList, getExerciseDetetail and getUserAnswerById
const teacherComment = (context) = >
  buildFlow(context, [
    Execute the child asynchronous queue after the parent asynchronous logic completes with the children attribute
    { ...getHomeWorkList, children: [getExerciseDetetail, getUserAnswerById] },
  ]);

// Get getHomeWorkList and getUserAnswerList at the same time, then get getExerciseDetetail
const studentReview = (context) = >
  // Use objects to define concurrent asynchronous logic and arrays to define queue asynchronous logicbuildFlow(context, { ... {... getHomeWorkList,children: [getExerciseDetetail] },
    getUserAnswerList,
  });
Copy the code

Once assembled, the asynchronous stream does not automatically execute and requires a call to SUBSCRIBE to run. So the final code to run is as follows:

const getMode = (mode, context) => {
  switch (mode) {
    case "studentRevise":
      return studentRevise(context);
    case "teacherComment":
      return teacherComment(context);
    case "studentPractice":
      return studentPractice(context);
    case "teacherPreviewFlow":
      returnteacherPreviewFlow(context); }}; // getMode()"studentPractice", { homeworId: "123"}).subscribe((data) => { data.getHomeWorkList; // Job list data.getexercisedetetail; Data.getuseranswerbyid; // user answer});Copy the code

The purpose of wrapping this library is to lower the barriers to RXJS usage (I wrote an introduction to RXJS earlier for reference). At present, the purpose has been achieved from the use of the project. In the future, I will improve the error handling. If you are interested, please click a star.

Async-flow: A wheel that helps us write complex asynchronous logic in a declarative code style