web-dev-qa-db-fra.com

Dois-je revenir après une résolution rapide / un refus?

Supposons que j'ai le code suivant.

function divide(numerator, denominator) {
 return new Promise((resolve, reject) => {

  if(denominator === 0){
   reject("Cannot divide by 0");
   return; //superfluous?
  }

  resolve(numerator / denominator);

 });
}

Si mon objectif est d'utiliser reject pour sortir tôt, devrais-je prendre l'habitude de returning immédiatement après?

192
sam

Le but return est de terminer l'exécution de la fonction après le rejet et d'empêcher l'exécution du code après celle-ci.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {

    if (denominator === 0) {
      reject("Cannot divide by 0");
      return; // The function execution ends here 
    }

    resolve(numerator / denominator);
  });
}

Dans ce cas, cela empêche la resolve(numerator / denominator); de s'exécuter, ce qui n'est pas strictement nécessaire. Cependant, il est toujours préférable de mettre fin à l'exécution afin d'éviter une éventuelle interruption dans le futur. En outre, il est recommandé d’empêcher de faire tourner du code inutilement.

Contexte

Une promesse peut être dans l’un des 3 états suivants:

  1. en attente - état initial. En attendant, nous pouvons passer à l’un des autres États
  2. accomplie - opération réussie
  3. rejeté - opération échouée

Lorsqu'une promesse est remplie ou rejetée, elle reste indéfiniment dans cet état (réglée). Ainsi, le rejet d'une promesse accomplie ou d'une promesse rejetée n'aura aucun effet.

Cet extrait de code montre que, bien que la promesse ait été remplie après avoir été rejetée, elle est restée rejetée.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }

    resolve(numerator / denominator);
  });
}

divide(5,0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Alors pourquoi avons-nous besoin de revenir?

Bien que nous ne puissions pas modifier un état de promesse établie, le rejet ou la résolution n'arrêtera pas l'exécution du reste de la fonction. La fonction peut contenir du code qui créera des résultats confus. Par exemple:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }
    
    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Même si la fonction ne contient pas ce code pour le moment, cela crée un piège futur. Un futur refactor peut ignorer le fait que le code est toujours exécuté après le rejet de la promesse et qu'il sera difficile à déboguer.

arrêt de l'exécution après résolution/rejet:

Il s’agit de choses standard du flux de contrôle JS.

  • Revenir après le resolve/reject:
function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
      return;
    }

    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));
  • Renvoyer avec la resolve/reject - puisque la valeur de retour du rappel est ignorée, nous pouvons enregistrer une ligne en retournant l'instruction de rejet/résolution:
function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      return reject("Cannot divide by 0");
    }

    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));
  • Utiliser un bloc if/else:
function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    } else {
      console.log('operation succeeded');
      resolve(numerator / denominator);
    }
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Je préfère utiliser l’une des options return car le code est plus plat.

274
Ori Drori

Un idiome courant, qui peut être votre tasse de thé ou non, est de combiner la return avec la reject, de rejeter simultanément la promesse et de quitter la fonction, de sorte que le reste de la fonction y compris le resolve n'est pas exécuté. Si vous aimez ce style, cela peut rendre votre code un peu plus compact.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) return reject("Cannot divide by 0");
                           ^^^^^^^^^^^^^^
    resolve(numerator / denominator);
  });
}

Cela fonctionne bien car le constructeur Promise ne fait rien avec aucune valeur de retour, et dans tous les cas, resolve et reject ne retournent rien.

Le même idiome peut être utilisé avec le style de rappel indiqué dans une autre réponse:

function divide(nom, denom, cb){
  if(denom === 0) return cb(Error("Cannot divide by zero"));
                  ^^^^^^^^^
  cb(null, nom / denom);
} 

Encore une fois, cela fonctionne bien car la personne qui appelle divide ne s'attend pas à ce qu'elle retourne quoi que ce soit et ne fait rien avec la valeur de retour.

29
user663031

Techniquement il n'est pas nécessaire ici1 - car une promesse peut être résolue ou rejetée, exclusivement et une seule fois . Le premier résultat de Promise gagne et chaque résultat ultérieur est ignoré. C'est différent des rappels de type nœud.

Cela étant dit, il est de bonne pratique propre de veiller à ce que l'on appelle exactement une personne, lorsque cela est pratique, et même dans ce cas puisqu'il n'y a plus d'autre async/différé En traitement. La décision de "revenir plus tôt" n'est pas différente de la fin d'une fonction lorsque son travail est terminé - vs. continuer sans relation ou traitement inutile.

Le renvoi au moment opportun (ou l'utilisation de conditions conditionnelles pour éviter d'exécuter le cas "autre") réduit le risque d'exécution accidentelle de code dans un état non valide ou d'effets secondaires indésirables; et en tant que tel, le code est moins enclin à "se briser de manière inattendue".


1 Cette réponse sur le plan technique dépend également du fait que dans ce cas le code après le "retour", devrait-il être omis, n'entraînera pas d'effet secondaire. JavaScript va heureusement se diviser par zéro et renvoyer + Infinity/-Infinity ou NaN.

9
user2864740

Si vous ne "retournez" pas après une résolution/un rejet, de mauvaises choses (comme une redirection de page) peuvent se produire après que vous vouliez vous arrêter. Source: je suis tombé dessus.

7
Benjamin H

La réponse d'Ori explique déjà qu'il n'est pas nécessaire de return mais c'est une bonne pratique. Notez que le constructeur de promesse est protégé contre les éjections. Il ignorera donc les exceptions renvoyées passées plus tard dans le chemin. Vous avez essentiellement des effets secondaires que vous ne pouvez pas observer facilement.

Notez que returning early est également très courant dans les rappels:

function divide(nom, denom, cb){
     if(denom === 0){
         cb(Error("Cannot divide by zero");
         return; // unlike with promises, missing the return here is a mistake
     }
     cb(null, nom / denom); // this will divide by zero. Since it's a callback.
} 

Ainsi, bien que ce soit une bonne pratique dans les promesses, il est obligatoire avec des rappels. Quelques notes sur votre code:

  • Votre cas d'utilisation est hypothétique. N'utilisez pas les promesses avec des actions synchrones.
  • Le constructeur de promesse ignore renvoie les valeurs. Certaines bibliothèques vous préviendront si vous renvoyez une valeur non indéfinie pour vous prévenir de l'erreur de revenir là-bas. La plupart ne sont pas si intelligents.
  • Le constructeur de promesse est en sécurité, il convertira les exceptions en rejets, mais comme d'autres l'ont souligné - une promesse est résolue une fois.
7
Benjamin Gruenbaum

Dans de nombreux cas, il est possible de valider les paramètres séparément et de renvoyer immédiatement une promesse rejetée avec Promise.reject (raison) .

function divide2(numerator, denominator) {
  if (denominator === 0) {
    return Promise.reject("Cannot divide by 0");
  }
  
  return new Promise((resolve, reject) => {
    resolve(numerator / denominator);
  });
}


divide2(4, 0).then((result) => console.log(result), (error) => console.log(error));
3
Dorad