1. Une logique moins élégante
"Réessayer" est un scénario très courant dans le développement frontal, mais en raison Promise
de 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é Promise
combiné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 async
and 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 await
de 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 async
la await
syntaxe 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 go
le 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 for
boucle , 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' Promise
envelopper !"
Pas de problème, on peut aller plus loin et écrire typescript
un 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);
}