Gérer la logique de nouvelle tentative dans le frontend avec élégance

1. Une logique moins élégante

"Réessayer" est un scénario très courant dans le développement frontal, mais en raison Promisede la fonctionnalité de rappel asynchrone de , nous pouvons facilement tomber dans "l'enfer du rappel" lorsqu'il s'agit de logique de nouvelle tentative, comme cette récursivité Promisecombinée à la fonction de test de nouvelle tentative :

// 工具函数,用于延迟一段时间
const sleep = (second: number = 1000) => {
    
    
  return new Promise((resolve) => {
    
    
    setTimeout(resolve, second);
  });
};

// 工具函数,用于递归重试函数
const retry = <T>(
  promise: () => Promise<T>,
  resolve: (value: unknown) => void,
  reject: (reason?: any) => void,
  retryTimes: number,
  retryMaxTimes: number
) => {
    
    
  sleep().then(() => {
    
    
    promise()
      .then((res: any) => {
    
    
        resolve(res);
      })
      .catch((err: any) => {
    
    
        if (retryTimes >= retryMaxTimes) {
    
    
          reject(err);
          return;
        }
        retry(promise, resolve, reject, retryTimes + 1, retryMaxTimes);
      });
  });
};

// 重试函数
export const retryRequest = <T>(
  promise: () => Promise<T>,
  retryMaxTimes: number
) => {
    
    
  return new Promise((resolve, reject) => {
    
    
    let count = 1;
    promise()
      .then((res) => {
    
    
        resolve(res);
      })
      .catch((err) => {
    
    
        if (count >= retryMaxTimes) {
    
    
          reject(err);
          return;
        }
        retry(promise, resolve, reject, count + 1, retryMaxTimes);
      });
  });
};

Avec asyncand await, la situation n'est guère meilleure, il faut encore utiliser la récursivité, et ce n'est pas très intuitif d'envelopper une couche de "try - catch" en dehors awaitde la fonction

// 工具函数,用于延迟一段时间
const sleep = (second: number = 1000) => {
    
    
  return new Promise((resolve) => {
    
    
    setTimeout(resolve, second);
  });
};

// 工具函数,用于递归重试函数
const retry = async <T>(
  promise: () => Promise<T>,
  retryTimes: number,
  retryMaxTimes: number
) => {
    
    
  await sleep();

  try {
    
    
    let result = await promise();
    return result;
  } catch (err) {
    
    
    if (retryTimes >= retryMaxTimes) {
    
    
      throw err;
    }
    retry(promise(), retryTimes + 1, retryMaxTimes);
  }
};

// 重试函数
export const retryRequest = async <T>(
  promise: () => Promise<T>,
  retryMaxTimes: number
) => {
    
    
  let count = 1;
  try {
    
    
    let result = await promise();
    return result;
  } catch (err) {
    
    
    if (count >= retryMaxTimes) {
    
    
      throw err;
    }
    retry(promise(), count + 1, retryMaxTimes);
  }
};

2. Logique élégante

Alors, comment écrire une logique de nouvelle tentative avec élégance ? Combiné avec la logique moins élégante de la première partie, nous pouvons savoir que si nous voulons améliorer la lisibilité, nous devons faire les trois choses suivantes :

1. Éliminer les rappels
2. Éliminer la logique de gestion des exceptions try-catch
3. Éliminer la logique récursive

2-1. Rappel et gestion des exceptions

Afin d'éliminer la fonction de rappel, nous choisissons toujours asyncla awaitsyntaxe et , et pour la gestion des exceptions, nous encapsulons une fonction pour niveler la logique de gestion des exceptions, comme suit :

const awaitErrorWrap = async <T, U = any>(
  promise: Promise<T>
): Promise<[U | null, T | null]> => {
    
    
  try {
    
    
    const data = await promise;
    return [null, data];
  } catch (err: any) {
    
    
    return [err, null];
  }
};

De cette façon, nous pouvons obtenir l'exception directement sans envelopper try-catch comme gole language , comme suit :

const [err, data] = await awaitErrorWrap(promise);

2-2. Changer la fonction récursive en boucle

Nous changeons la fonction récursive d'origine en une forboucle , pour que la logique soit plus claire :

const retryRequest = async <T>(
  promise: () => Promise<T>,
  retryTimes: number = 3
) => {
    
    
  let output: [any, T | null] = [null, null];

  for (let a = 0; a < retryTimes; a++) {
    
    
    output = await awaitErrorWrap(promise());

    if (output[1]) {
    
    
      break;
    }
  }

  return output;
};

2-3. Logique de nouvelle tentative élégante

Ensuite, nous combinons les codes ci-dessus pour obtenir la logique de nouvelle tentative très concise et élégante suivante :

// 工具函数,用于延迟一段时间
const sleep = (time: number) => {
    
    
  return new Promise((resolve) => {
    
    
    setTimeout(resolve, time);
  });
};

// 工具函数,用于包裹 try - catch 逻辑
const awaitErrorWrap = async <T, U = any>(
  promise: Promise<T>
): Promise<[U | null, T | null]> => {
    
    
  try {
    
    
    const data = await promise;
    return [null, data];
  } catch (err: any) {
    
    
    return [err, null];
  }
};

// 重试函数
export const retryRequest = async <T>(
  promise: () => Promise<T>,
  retryTimes: number = 3,
  retryInterval: number = 500
) => {
    
    
  let output: [any, T | null] = [null, null];

  for (let a = 0; a < retryTimes; a++) {
    
    
    output = await awaitErrorWrap(promise());

    if (output[1]) {
    
    
      break;
    }

    console.log(`retry ${
      
      a + 1} times, error: ${
      
      output[0]}`);
    await sleep(retryInterval);
  }

  return output;
};

Nous pouvons l'utiliser comme ceci :

import {
    
     retryRequest } from "xxxx";
import axios from "axios";

const request = (url: string) => {
    
    
  return axios.get(url);
};

const [err, data] = await retryRequest(request("https://request_url"), 3, 500);

2-4. Aller plus loin ? Écrire un décorateur

Bien sûr, après avoir écrit ici, certains amis diront certainement : "Ce n'est pas assez élégant d' Promiseenvelopper !"

Pas de problème, on peut aller plus loin et écrire typescriptun décorateur pour :

export const retryDecorator = (
  retryTimes: number = 3,
  retryInterval: number = 500
): MethodDecorator => {
    
    
  return (_: any, __: string | symbol, descriptor: any) => {
    
    
    const fn = descriptor.value;
    descriptor.value = async function (...args: any[]) {
    
    
      // 这里的 retryRequest 就是刚才的重试函数
      return retryRequest(fn.apply(this, args), retryTimes, retryInterval);
    };
  };
};

Nous pouvons donc utiliser ce décorateur directement comme ceci :

import {
    
     retryRequest } from "xxxx";
import axios from "axios";

class RequestClass {
    
    
  @retryDecorator(3, 500)
  static async getUrl(url: string) {
    
    
    return axios.get(url);
  }
}

const [err, data] = await RequestClass.getUrl("https://request_url");

2-5. Que diriez-vous d'une optimisation supplémentaire ?

Certaines personnes peuvent penser que lancer l'exception directement en tant que variable n'est pas intuitif, pas de problème, passons à une version qui peut utiliser try catch normalement :

// 重试函数
export const retryRequest = async <T>(
  promise: () => Promise<T>,
  retryTimes: number = 3,
  retryInterval: number = 500
) => {
    
    
  let output: [any, T | null] = [null, null];

  for (let a = 0; a < retryTimes; a++) {
    
    
    output = await awaitErrorWrap(promise());

    if (output[1]) {
    
    
      break;
    }

    console.log(`retry ${
      
      a + 1} times, error: ${
      
      output[0]}`);
    await sleep(retryInterval);
  }

  if (output[0]) {
    
    
    throw output[0];
  }

  return output[1];
};

Nous pouvons l'utiliser normalement comme une capture d'essai:

import {
    
     retryRequest } from "xxxx";
import axios from "axios";

const request = (url: string) => {
    
    
  return axios.get(url);
};

try {
    
    
  const res = await retryRequest(request("https://request_url"), 3, 500);
} catch (err) {
    
    
  console.error(res);
}

Je suppose que tu aimes

Origine blog.csdn.net/u011748319/article/details/123828513
conseillé
Classement