web-dev-qa-db-fra.com

Délai d'attente en async / attente

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?

21
nkint

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.

35
Bergi

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.

4
nkint

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.

0
Alexander Danilov