Je suis avec Node.js et TypeScript et j'utilise async/await
. Voici mon cas de test:
async function doSomethingInSeries() {
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
return 'simle';
}
Je voudrais définir un délai d'expiration pour la fonction globale. C'est à dire. si res1
prend 2 secondes, res2
prend 0,5 seconde, res3
prend 5 secondes J'aimerais avoir un timeout qui après 3 secondes me permette de lancer une erreur.
Avec un appel setTimeout
normal, c'est un problème car la portée est perdue:
async function doSomethingInSeries() {
const timerId = setTimeout(function() {
throw new Error('timeout');
});
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
clearTimeout(timerId);
return 'simle';
}
Et je ne peux pas l'attraper avec une normale Promise.catch
:
doSomethingInSeries().catch(function(err) {
// errors in res1, res2, res3 will be catched here
// but the setTimeout thing is not!!
});
Des idées sur la façon de résoudre?
Vous pouvez utiliser Promise.race
pour faire un timeout:
Promise.race([
doSomethingInSeries(),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 11.5e3))
]).catch(function(err) {
// errors in res1, res2, res3 and the timeout will be caught here
})
Vous ne pouvez pas utiliser setTimeout
sans l'envelopper dans une promesse.
Ok j'ai trouvé ça:
async function _doSomethingInSeries() {
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
return 'simle';
}
async function doSomethingInSeries(): Promise<any> {
let timeoutId;
const delay = new Promise(function(resolve, reject){
timeoutId = setTimeout(function(){
reject(new Error('timeout'));
}, 1000);
});
// overall timeout
return Promise.race([delay, _doSomethingInSeries()])
.then( (res) => {
clearTimeout(timeoutId);
return res;
});
}
Des erreurs?
Ce qui me sent un peu, c'est que l'utilisation de Promises comme stratégie d'asynchronicité nous enverra pour allouer trop d'objets dont une autre stratégie a besoin, mais c'est hors sujet.
Problème avec @Bergi répond que doSomethingInSeries
continue de s'exécuter même si vous avez déjà rejeté la promesse. Il est préférable d’annuler par expiration de délai.
Voici le support de l'annulation:
async function doSomethingInSeries(cancellationToken) {
cancellationToken.throwIfCancelled();
const res1 = await callApi();
cancellationToken.throwIfCancelled();
const res2 = await persistInDB(res1);
cancellationToken.throwIfCancelled();
const res3 = await doHeavyComputation(res1);
cancellationToken.throwIfCancelled();
return 'simle';
}
Mais il est encore mieux de passer un jeton d'annulation à chaque fonction asynchrone et de l'utiliser là-bas.
Voici la mise en œuvre de l'annulation:
let cancellationToken = {
cancelled: false,
cancel: function() {
this.cancelled = true;
},
throwIfCancelled: function() {
if (this.cancelled) throw new Error('Cancelled');
}
}
Vous pouvez l'envelopper en classe si vous le souhaitez.
Et enfin l'utilisation:
doSomethingInSeries(cancellationToken);
setTimeout(cancellationToken.cancel, 5000);
Gardez à l'esprit que la tâche n'est pas annulée immédiatement, donc la poursuite (en attente, puis ou catch) n'est pas appelée exactement après 5 secondes. Pour garantir que vous pouvez combiner cette approche et l'approche @Bergi.