2
votes

Comment appeler la fonction à intervalle fixe en s'assurant que la fonction précédente est terminée

J'ai une fonction basée sur la promesse que je veux appeler à intervalle fixe, disons toutes les 60 secondes. Mais je veux aussi m'assurer que cette fonction n'est appelée que si la fonction précédemment appelée est exécutée complètement et je veux continuer cela pendant un temps infini

function fun(){
    return new Promise((resolve,reject)=>{
        //Some database queries which may or may not complete in 60 seconds
        resolve("done")
    })
}

setInterval(()=>{
    fun().then(d=>{
        console.log(d)
    })
},60000)

Le code ci-dessus ne vérifiera pas la fonction précédemment appelée est terminé ou non. mais je veux m'assurer que


21 commentaires

Si vous voulez attendre la fin, vous devez passer à setTimeout à la place


Utilisez simplement setTimeout au lieu de setInterval et appelez à nouveau.


Utilisez une variable globale pour savoir si le plaisir s'exécute. Définissez-le sur true lorsqu'il est appelé, définissez-le sur false lorsque la promesse est résolue. Si le drapeau est véridique, n'appelez pas fun dans l'intervalle, sinon, appelez-le. Sinon, si le timing ne vous dérange pas, utilisez simplement setTimeout à la place. De plus, il n'y a pas d'attente asynchrone dans votre code, est-ce indended? (il est étiqueté)


@briosheje Sachez cependant qu'en faisant cela, vous devrez peut-être attendre près de 2 minutes avant la prochaine vérification, ou la prochaine vérification pourrait avoir lieu instantanément après la dernière.


@Icepickle OP veut qu'il s'exécute toutes les 60 secondes, pendant un temps infini.


@Mirakurun je comprends ça, j'ai aussi posté une réponse pour montrer comment il pouvait le faire;)


@Keith, cela ne devrait jamais arriver, car l'affectation de variable devrait prendre très peu de cycles d'horloge, donc la chance est si faible que je ne pense pas que vous devriez jamais envisager ce scénario (si vous parlez de concurrence, sinon, très bien). Le fait est qu'avoir un intervalle de 60 secondes est tout à fait… inutile? (pour cette approche en particulier)


" peut ou peut ne pas se terminer en 60 secondes " Les navigateurs abandonnent généralement, par exemple un appel AJAX après 30 secondes automatiquement, si le serveur n'a pas répondu dans ce délai.


@Icepickle Si j'utilise setTimeout au lieu de setInterval, il ne s'assurera toujours pas que la fonction appelée précédente est terminée


Oui, car vous lui dites de recommencer lorsque vous avez terminé le précédent.


@Teemu C'est pour le nœud js, cette fonction ne sera pas utilisée dans les navigateurs


@Keith Merci pour les suggestions mais c'est exactement ce que je veux faire


@briosheje 1. requête de base de données, prend 59 secondes, le prochain setInterval démarre, requête de base de données à nouveau. IOW: 1 seconde entre la requête précédente et la suivante, si c'est ce que veut l'OP, c'est très bien. Si vous utilisez un drapeau, il en manquera bien sûr aussi. les requêtes prennent 1 min 1 seconde, la deuxième minute ne sera pas exécutée.


@KrunalSonparate Désolé, qu'est-ce que tu veux faire?,. Comme je le vois, vous avez 2 options, si vous utilisez setInterval, il déclenchera votre requête DB initiale toutes les 1 min, uniquement si la dernière a été effectuée dans les 1 min, sinon il manquera l'intervalle suivant. Vous pouvez également dire "doquery" - "wait1min" - "doquery" - "wait1min", c'est mieux servi avec setTmeout. Personnellement, j'utilise la méthode setTimeout la plupart du temps, car j'aime l'idée de donner au moteur de base de données un espace de respiration garanti entre les requêtes, si ces requêtes sont lourdes, ou vous pourriez finir par faire 2 requêtes lourdes l'une après l'autre. :(


@KrunalSonparate Ah ... OK. Node.js n'est cependant pas mentionné dans la question.


@Keith Je veux faire un peu de traitement en db toutes les 60 secondes. donc le problème est que si j'utilise setInterval avec toutes les 60 secondes, il appellera à doquery toutes les 60 secondes, mais il continuera à faire une requête si la requête de données précédente n'est pas terminée. donc je veux juste m'assurer que la requête est effectuée toutes les 60 secondes et si la requête prend un temps de 65 secondes, cette requête est après 65 secondes, mais si la requête est terminée en 40 secondes, je veux effectuer une requête après 20 secondes de requête précédente


Je serai heureux si je peux utiliser mutex ici. ça fonctionnera pour moi


Donc, si la requête prend 65 secondes, que voulez-vous faire ce n'est pas clair. 1. Exécutez la requête suivante instantanément, 2. attendez 55 secondes pour la suivante ?. Si vous en faites 1, vous pourriez bien sûr finir par courir après votre queue. Je ne sais pas comment un mutex va vous aider ici, cela n'a rien à voir avec le threading. Il vaut également la peine de penser au pire des cas, si votre requête prend constamment 59 secondes pour se terminer, vous allez essentiellement fixer votre serveur DB à 100% en traitant cette requête en permanence.


Si la requête utilise 65 secondes, je souhaite appeler la requête suivante instantanément. Je suis sûr qu'à chaque fois, cela ne prendra pas plus de 59 secondes


Ah, dans ce cas, vous devrez utiliser une pile. Mais soyez conscient du problème de la poursuite de la queue, cette pile pourrait finir par devenir de plus en plus grande. par exemple .. Si chaque requête prend 65 secondes, la pile ne fera que croître et grandir. Si c'est quelque chose que vous voulez vraiment, je pourrais publier un simple extrait de code montrant comment vous pouvez le faire. Et bien sûr, si cette pile est toujours pleine, vous exécutez la requête en permanence.


@Krunal alors évitez simplement d'utiliser des intervalles. Faites une file d'attente ou quelque chose comme ça.


3 Réponses :


1
votes

Eh bien, si vous voulez attendre la fin, vous devriez le rappeler une fois la promesse résolue. Vous passeriez donc de setInterval à setTimeout à la place.

Pour les besoins de cette question, j'ai changé le délai d'expiration à 1 seconde à la place

function fun(){
  fun.running = true;
  return new Promise((resolve,reject)=>{
      //Some database queries which may or may not complete in 60 seconds
      resolve("done");
  });
}

setInterval(()=>{
  // just make sure the fun isn't running
  if (!fun.running) {
    fun()
      .then(d=> console.log(d) )
      .catch( err => console.log( err ) ) // error handling here to make sure the next block is run
      .then( () => {
        // and handle the reset of the flag here
        fun.running = false;
      } );
  }
},1000);

Bien sûr, choisissez un meilleur nom que self , c'était comme la seule chose que je pouvais trouver à court terme :)

Mise à jour

Je pense que j'ai mal compris la question à l'origine, donc vous ne voulez la rappeler que si elle s'est vraiment terminée, et n'attendez pas qu'elle se termine et que le nouvel intervalle commence.

Dans ce cas, vous pouvez plutôt faire quelque chose comme ceci à la place:

function fun(){
    return new Promise((resolve,reject)=>{
        //Some database queries which may or may not complete in 60 seconds
        resolve("done")
    })
}

function self() {
  setTimeout(()=>{
      fun().then(d=>{
          console.log(d)
      }).then( self ); // call the setTimeout function again
  },1000);
}

self();


2 commentaires

Cette solution ne fonctionnera pas pour moi. Cela ajoutera également le temps d'exécution de cette fonction, de sorte qu'il ne sera pas appelé toutes les 1000 ms


@KrunalSonparate Désolé, je crois que j'ai mal compris votre question initiale, mais j'ai mis à jour ma réponse pour la marquer à l'aide d'un drapeau sur la fonction



1
votes

Au lieu d'appeler la fonction dans setInterval, appelez-la avec setTimeout après réception de la réponse.

function fun(){
    return new Promise((resolve,reject)=>{
        //Some database queries which may or may not complete in 60 seconds
        resolve("done")
    })
}
//Function to call promise function recursively
function functionCaller(fn){
fn()
.then((response)=>{
   console.log(response)
   setTimeout(() => functionCaller(fn), 6000)
})
}

functionCaller(fun)


0 commentaires

2
votes

let queue;
function push(task) {
  return queue = queue ? queue.then(() => task()) : task();
}
// or
const push = (task) => queue = queue ? queue.then(() => task()) : task();

Bien sûr, nous pouvons l'implémenter sans utiliser de classe

class AsyncQueue {
  constructor() {
    this.queue = null;
  }

  push(task) {
    // If we have task in query, append new at the end, if not execute immediately
    // Every task appended to existing queue will be executed immediately after previous one is finished
    return this.queue = this.queue ? this.queue.then(() => task()) : task();
  }
}

const task = (id) => () => new Promise((resolve) => {
  console.log('Task', id, 'started');
  // Random time betwen 500-1300ms
  const time = Math.round(Math.random() * 800 + 500);
  setTimeout(() => {
    console.log("Finished task '" + id + "' after " + time + "ms");
    resolve();
  }, time);
});
const queue = new AsyncQueue();
let id = 0;
// This will push new task to queue every 1s
setInterval(() => {
  console.log("Pushing new task", ++id);
  queue.push(task(id));
}, 1000);


3 commentaires

Belle solution :)


@Icepickle merci, je n'aime pas compliquer les choses


@ ponury-kostek Merci pour la solution :)