The recent development task is severe, in the crazy writing code (less bugs) at the same time also have to spare time with the backend docking, so there is the following dialogue

I: This page contains a total of name, ID, time, address, data five fields, you who provide interface ah! Xiaoming: I provide an interface to return id, name and time fields to you. Xiao Hong: I also provide an interface, I return you the address field. Zhang: Me too. I’ll give you the data field. I: you are in embarrass me fat tiger?? I have to request three interfaces for five fields, you can’t aggregate it! We are now micro services can only provide so much, the front end of their aggregation me: pawn ···

The above example is often encountered in development and can be solved easily, but how to ensure the integrity of the page is a problem.

🌰

We need to request three interfaces, namely interface A, interface B and interface C. During the request process, A succeeded but B and C failed due to service instability. In this case, we either report the error message directly, which is kind of unfriendly; Either use only the data returned by A, in which case there will be default values at the front, and the page will naturally display the default information, and the user will use the wrong data to make the wrong decision, which is a very serious mistake. In order to solve the above problems, this paper implements a fault tolerance mechanism to try to solve this problem.

implementation

Retry scheme

The retry first mechanism means that we need multiple failed requests to the interface. To solve this problem, there are about two solutions:

  • socket
  • polling

But we don’t have back end support, socket just dead. Polling is the only way to try. Post the polling code first

export interface IParams { maxCount? : number; // Maximum number of polls intervalTime? : number; // Increase the length of time for each poll maxInterval? : number; } export interface IProcessPayload<T> {data: T; count: number; resolve? : (data: T) => void; reject? : (err: any) => void; } / * * * * * error failure process continue polling * finish end polling * / export type IProgressType = 'error' | 'process' | 'finish'. const defaultConfig = { maxCount: 120, intervalTime: 1000, maxInterval: 1600, }; export class PollingFun { timeoutTimer: any; cancelWhile: any; constructor(private config: IParams = { maxCount: 120, intervalTime: 1000, maxInterval: 1600 }) { this.config = { ... defaultConfig, ... config }; } cancel() { if (this.cancelWhile) { this.cancelWhile(); this.cancelWhile = null; } if (this.timeoutTimer) { clearTimeout(this.timeoutTimer); } } pollingSingleTask = async <T>(onProgress: (data: IProcessPayload<T>) => IProgressType, ajaxFun: () => Promise<T>) => { const { maxCount, intervalTime, maxInterval } = this.config; let pollingCount = 0; let stopPolling = false; this.cancel(); this.cancelWhile = () => (stopPolling = true); while (! StopPolling && pollingCount < maxCount) {// Start intensive, then longer interval, up to 1s. let realIntervalTime = Math.floor(pollingCount / 10) * 200 + intervalTime; // eslint-disable-line realIntervalTime = Math.min(realIntervalTime, maxInterval); try { const resData = await ajaxFun(); if (stopPolling) { return Promise.reject('cancel'); } const progressRes = onProgress({ data: resData, count: pollingCount }); switch (progressRes) { case 'finish': stopPolling = true; return Promise.resolve(resData); case 'error': stopPolling = true; return Promise.reject(resData); default: await new Promise(resolve => { this.timeoutTimer = setTimeout(resolve, realIntervalTime); }); break; } } catch (error) { stopPolling = true; return Promise.reject(error); } pollingCount += 1; } if (pollingCount >= maxCount) { return Promise.reject('overMaxCount'); }}; }Copy the code

As you can see, we have implemented a polling class, which is very simple to use, just one new instance at a time, and then call the corresponding method. The polling method needs two parameters, one is the polling processing function, which accepts a parameter, will carry the data of the polling, we only need to judge the data, and then return the corresponding data processing polling.

const pollInstance = new PollingFun();
pollInstance.pollingSingleTask(process, ajaxFun);
Copy the code

Retry mechanism

As we can see from the above background, a successful request means that all requests return a result, and we turn our head and think of promise.all, solving half the problem in an instant. All we need to do is wrap each request function in a polling mode and wait for the value to be picked up.

type AjaxFun<T> = [() => Promise<T>, (data: IProcessPayload<T>) => IProgressType, IParams]; const createPromise = <T>(ajaxFunArr: AjaxFun<T>[]) => { return ajaxFunArr.map(item => { const [ajaxFn, onProcess, options] = item; const pollInstance = new PollingFun(options); return new Promise((resolve, reject) => { pollInstance.cancel(); pollInstance .pollingSingleTask(payload => onProcess({ ... payload, resolve, reject }), ajaxFn) .catch(err => reject(err)); }); }); }; export const ajaxCatch = async <T>(ajaxFunArr: AjaxFun<T>[]) => { const wrapAjaxFunArr = await createPromise(ajaxFunArr); return Promise.all([...wrapAjaxFunArr]) .then(res => ({ status: true, data: res, })) .catch(err => ({ status: false, data: err, })); };Copy the code

As you can see, we’ve wrapped an Ajax handler function that takes an array type parameter, and each array subvalue needs to provide three values: the current request function, the function that controls the polling state, and the polling initialization value. It looks good. Try it out

const wake = async val => {
  console.log(val);
  return await val;
};
const onProcess = pay => {
  const { data, resolve, count } = pay;
  if (data === 'q2' && count === 3) {
    resolve(data);
    return 'finish';
  }
  if (data === 'q2') {
    return 'process';
  }
  return 'finish';
};
export const getData = async () => {
  const res = await ajaxCatch([
    [() => wake('q1'), onProcess, { maxCount: 5 }],
    [() => wake('q2'), onProcess, { maxCount: 5 }],
    [() => wake('q3'), onProcess, { maxCount: 5 }],
  ]);
  console.log(res, 'cdc');
};
Copy the code

Perfect to achieve the effect we need!

conclusion

The above implementation can retry the failed interface many times, and return the results uniformly, the user will not see the page flicker and other problems, but also can be used as a polling function, it can kill two birds with one stone! So, don’t panic when you encounter problems, analyze them slowly and solve them step by step!