Exploring an asynchronous serial interview question of Ant Financial Service worth 25k

  • 2021-08-06 20:43:34
  • OfStack

Preface

When my friend went to interview Ant Financial, he met an interview question. At first glance, it felt quite simple, but there were still many points worth mentioning internally.

Look at the topic first:


const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const subFlow = createFlow([() => delay(1000).then(() => log("c"))]);

createFlow([
 () => log("a"),
 () => log("b"),
 subFlow,
 [() => delay(1000).then(() => log("d")), () => log("e")],
]).run(() => {
 console.log("done");
});

//  Need to follow  a,b, Delay 1 Seconds ,c, Delay 1 Seconds ,d,e, done  Sequential printing of 

Implement createFlow according to the above test case:

flow is a logical fragment consisting of a series of effects. flow supports nesting. effects execution only needs to support serial.

Analysis

First, by analyzing the parameters, createFlow accepts an array as a parameter (according to the meaning of the question, every item should be called effect), and excludes one duplicate item. We classify every item in the parameter array into one, which has the following types in total:

Common function:


() => log("a");

Delay function (Promise):


() => delay(1000).then(() => log("d"));

Another flow:


const subFlow = createFlow([() => delay(1000).then(() => log("c"))]);

The above three items wrapped in an array.

Realization

First of all, a shallow copy of the parameters (write library functions, try not to affect the parameters passed in by users is a principle), and then simply flat flat 1. (Handling Situation 4)


function createFlow(effects = []) {
 let sources = effects.slice().flat();
}

Looking at the meaning of the question, createFlow does not start the method execution, and it takes. run () to start the execution, so define this function first:


function createFlow(effects = []) {
 let sources = effects.slice().flat();
 function run(callback) {
  while (sources.length) {
   const task = sources.shift();
  }
  callback?.();
 }
}

Here I choose to use the while loop to process each effect in the array in turn, which is convenient for interrupting at any time.

For effect of function type, execute it directly:


function createFlow(effects = []) {
 let sources = effects.slice().flat();
 function run(callback) {
  while (sources.length) {
   const task = sources.shift();
   if (typeof task === "function") {
    const res = task();
   }
  }
  //  After all tasks are completed,   Execute the callback function passed in 
  callback?.();
 }

 return {
  run,
  isFlow: true,
 };
}

Here we get the return value of the function res. Don't forget one case, that is, effect returns an Promise, such as this case:


() => delay(1000).then(() => log("d"));

Then, after getting the return value, the judgment is simplified directly here to see if the return value has then attribute to judge whether it is an Promise (please choose a more rigorous method for production environment).


if (res?.then) {
 res.then(createFlow(sources).run);
 return;
}

Here I choose to interrupt this flow execution, and use the remaining sources to create a new flow, and asynchronously open the new flow run in the then method of the previous Promise.

In this way, after the above Promise delayed by 1s is replaced by resolve, the remaining sources task array will be driven by the new flow. run () and continue to execute.
Next, we will deal with the case that effect is another flow. Notice the approximate function body written above. We have taken the return value of createFlow with isFlow

This flag is used to determine whether it is an flow.


//  Put callback Put it down 1 A flow Adj. callback Execute in timing 
const next = () => createFlow(sources).run(callback)
if (typeof task === "function") {
 const res = task();
 if (res?.then) {
  res.then(next);
  return;
 }
} else if (task?.isFlow) {
 task.run(next);
 return;
}

Look at the section of else if, directly call the run of the incoming flow, create a new flow with the remaining sources, and put the callback of this round into the callback position of the new flow. Do it after all the tasks are finished.

Defines an next method to be used when an asynchronous task or another flow is encountered

In this way, after the flow passed in the parameter is executed, the remaining tasks will continue to be executed, and callback will be executed at the end.

Complete code


() => log("a");
0

Summarize

The main purpose of this interview question is to investigate the control of asynchronous serial flow. Clever use of its own recursive design to deal with the incoming parameters is also an flow. In the process of writing the question, show your skilled use of Promise. 1 will definitely make the interviewer look at you with new eyes and ears ~


Related articles: