web-dev-qa-db-fra.com

Comment attendre une promesse JavaScript à résoudre avant de reprendre la fonction?

Je fais des tests unitaires. L'infrastructure de test charge une page dans un iFrame, puis exécute des assertions sur cette page. Avant que chaque test ne commence, je crée un Promise qui définit l'événement onload de l'iFrame à appeler resolve(), définit le src de l'iFrame et renvoie la promesse.

Donc, je peux simplement appeler loadUrl(url).then(myFunc), et il attendra que la page se charge avant d'exécuter quoi que ce soit myFunc.

J'utilise ce type de motif un peu partout dans mes tests (pas seulement pour le chargement d'URL), principalement pour permettre que des modifications soient apportées au DOM (par exemple, cliquer sur un bouton et attendre que les divs se cachent et s'affichent).

L'inconvénient de cette conception est que j'écris constamment des fonctions anonymes contenant quelques lignes de code. De plus, pendant que j'ai une solution de contournement (assert.async() de QUnit), la fonction de test qui définit les promesses se termine avant que la promesse ne soit exécutée.

Je me demande s’il est possible d’obtenir une valeur d’un Promise ou d’attendre (bloquer/dormir) jusqu’à ce qu’elle soit résolue, comme le IAsyncResult.WaitHandle.WaitOne() de .NET. Je sais que JavaScript est mono-thread, mais j'espère que cela ne signifie pas qu'une fonction ne peut pas céder.

Essentiellement, y a-t-il un moyen d’obtenir les résultats suivants dans le bon ordre?

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
};

function getResultFrom(promise) {
  // todo
  return " end";
}

var promise = kickOff();
var result = getResultFrom(promise);
$("#output").append(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>
65
dx_over_dt

Je me demande s'il existe un moyen d'obtenir une valeur d'une promesse ou d'attendre (bloquer/dormir) jusqu'à ce qu'elle soit résolue, de manière similaire à IAsyncResult.WaitHandle.WaitOne () de .NET. Je sais que JavaScript est mono-thread, mais j'espère que cela ne signifie pas qu'une fonction ne peut pas céder.

La génération actuelle de Javascript dans les navigateurs ne possède pas de wait() ou sleep() permettant d’exécuter d’autres tâches. Donc, vous ne pouvez simplement pas faire ce que vous demandez. Au lieu de cela, il a des opérations asynchrones qui vont faire leur chose et ensuite vous appeler quand elles sont faites (comme vous utilisez des promesses pour).

Une partie de ceci est dû au threadedness simple de Javascript. Si le fil unique est en train de tourner, aucun autre Javascript ne peut être exécuté tant que ce fil n'est pas terminé. ES6 présente yield et des générateurs qui permettent certaines astuces de ce type, mais nous sommes bien loin de pouvoir les utiliser dans un large éventail de navigateurs installés (ils peuvent être utilisés dans certains développements côté serveur où vous contrôlez le moteur JS utilisé).


Une gestion attentive du code fondé sur les promesses permet de contrôler l'ordre d'exécution de nombreuses opérations asynchrones.

Je ne suis pas sûr de comprendre exactement quel ordre vous essayez d'atteindre dans votre code, mais vous pouvez faire quelque chose comme ceci en utilisant votre fonction existante kickOff(), puis en y attachant un gestionnaire .then() après l'appelant:

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");

    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
}

kickOff().then(function(result) {
    // use the result here
    $("#output").append(result);
});

Cela retournera la sortie dans un ordre garanti - comme ceci:

start
middle
end

Mise à jour en 2018 (trois ans après cette réponse):

Si vous transpilez votre code ou si vous l'exécutez dans un environnement prenant en charge les fonctionnalités ES7 telles que async et await, vous pouvez désormais utiliser await pour que votre code "apparaisse" et attend le résultat d'une promesse. Il se développe encore avec des promesses. Il ne bloque toujours pas tout le Javascript, mais vous permet d'écrire des opérations séquentielles dans une syntaxe plus conviviale.

Au lieu de la manière de faire ES6:

someFunc().then(someFunc2).then(result => {
    // process result here
}).catch(err => {
    // process error here
});

Tu peux le faire:

// returns a promise
async function wrapperFunc() {
    try {
        let r1 = await someFunc();
        let r2 = await someFunc2(r1);
        // now process r2
        return someValue;     // this will be the resolved value of the returned promise
    } catch(e) {
        console.log(e);
        throw e;      // let caller know the promise was rejected with this reason
    }
}

wrapperFunc().then(result => {
    // got final result
}).catch(err => {
    // got error
});
58
jfriend00

Si vous utilisez ES2016, vous pouvez utiliser async et await et effectuer les opérations suivantes:

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())

Si vous utilisez ES2015, vous pouvez utiliser Générateurs . Si vous n'aimez pas la syntaxe, vous pouvez la résumer à l'aide de la fonction async utility = as expliqué ici .

Si vous utilisez ES5, vous voudrez probablement une bibliothèque telle que Bluebird pour vous donner plus de contrôle.

Enfin, si votre environnement d'exécution prend déjà en charge ES2015, l'ordre d'exécution peut être conservé avec le parallélisme à l'aide de Fetch Injection .

11
Josh Habdas

Une autre option consiste à utiliser Promise.all pour attendre un tableau de promesses à résoudre. Le code ci-dessous montre comment attendre que toutes les promesses soient résolues, puis traiter les résultats une fois qu'ils sont tous prêts (car cela semblait être l'objectif de la question); Cependant, start pourrait évidemment sortir avant que le milieu ne soit résolu en ajoutant simplement ce code avant d'appeler la résolution.

function kickOff() {
  let start = new Promise((resolve, reject) => resolve("start"))
  let middle = new Promise((resolve, reject) => {
    setTimeout(() => resolve(" middle"), 1000)
  })
  let end = new Promise((resolve, reject) => resolve(" end"))

  Promise.all([start, middle, end]).then(results => {
    results.forEach(result => $("#output").append(result))
  })
}

kickOff()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>
3
Stan Kurdziel

Vous pouvez le faire manuellement. (Je sais que ce n'est pas une bonne solution, mais ..) utilisez _ la boucle while jusqu'à ce que la result n'ait pas de valeur

kickOff().then(function(result) {
   while(true){
       if (result === undefined) continue;
       else {
            $("#output").append(result);
            return;
       }
   }
});
0
Guga Nemsitsveridze