Différence entre "retour en attente" et "retour promesse"

106

Compte tenu des exemples de code ci-dessous, y a-t-il une différence de comportement et, si oui, quelles sont ces différences?

return await promise

async function delay1Second() {
  return (await delay(1000));
}

return promise

async function delay1Second() {
  return delay(1000);
}

Si je comprends bien, le premier aurait une gestion des erreurs dans la fonction asynchrone, et les erreurs sortiraient de la promesse de la fonction async. Cependant, la seconde nécessiterait un tick de moins. Est-ce correct?

Cet extrait de code n'est qu'une fonction courante pour renvoyer une promesse à titre de référence.

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}
PitaJ
la source
3
Oui, j'ai édité ma question parce que vous avez mal compris ma signification et que cela ne répondait pas vraiment à ce que je me demandais.
PitaJ
1
@PitaJ: Je crois que vous vouliez supprimer le asyncde votre second ( return promise) échantillon.
Stephen Cleary
1
@PitaJ: Dans ce cas, votre deuxième exemple renverrait une promesse résolue par une promesse. Plutôt bizarre.
Stephen Cleary
5
jakearchibald.com/2017/await-vs-return-vs-return-await est un bel article qui résume les différences
sanchit
2
@StephenCleary, je suis tombé dessus et j'ai d'abord pensé exactement la même chose, une promesse qui se résout avec une promesse n'a pas de sens ici. Mais à mesure qu'il tourne, promise.then(() => nestedPromise)s'aplatirait et "suivrait" le nestedPromise. Intéressant à quel point c'est différent des tâches imbriquées dans C # où nous aurions à le Unwrapfaire. D'un autre côté, il semble que cela await somePromise appelle Promise.resolve(somePromise).then, plutôt que juste somePromise.then, avec quelques différences sémantiques intéressantes.
noseratio

Réponses:

152

La plupart du temps, il n'y a pas de différence observable entre returnet return await. Les deux versions de delay1Secondont exactement le même comportement observable (mais selon l'implémentation, la return awaitversion peut utiliser un peu plus de mémoire car un Promiseobjet intermédiaire peut être créé).

Cependant, comme @PitaJ l'a souligné, il y a un cas où il y a une différence: si le returnou return awaitest imbriqué dans un bloc try- catch. Prenons cet exemple

async function rejectionWithReturnAwait () {
  try {
    return await Promise.reject(new Error())
  } catch (e) {
    return 'Saved!'
  }
}

async function rejectionWithReturn () {
  try {
    return Promise.reject(new Error())
  } catch (e) {
    return 'Saved!'
  }
}

Dans la première version, la fonction async attend la promesse rejetée avant de renvoyer son résultat, ce qui fait que le rejet est transformé en exception et la catchclause est atteinte; la fonction renverra donc une promesse résolvant la chaîne "Saved!".

Cependant, la deuxième version de la fonction renvoie la promesse rejetée directement sans l'attendre dans la fonction asynchrone , ce qui signifie que le catchcas n'est pas appelé et que l'appelant obtient le rejet à la place.

Denis Washington
la source
Peut-être aussi mentionner que la trace de pile serait différente (même sans essai / capture)? Je pense que c'est le problème que les gens rencontrent le plus souvent dans cet exemple:]
Benjamin Gruenbaum
J'ai trouvé dans un scénario, que l'utilisation return new Promise(function(resolve, reject) { })dans une for...ofboucle, puis l'appel resolve()dans la boucle après un pipe()ne suspend pas l'exécution du programme jusqu'à ce que le tuyau soit terminé, comme souhaité, mais l'utilisation le await new Promise(...)fait. cette dernière syntaxe est-elle même valide / correcte? est-ce un «raccourci» pour return await new Promise(...)? pourriez-vous m'aider à comprendre pourquoi le second fonctionne et le premier non? pour le contexte, le scénario est solution 02de cette réponse
user1063287
10

Comme d'autres réponses l'ont mentionné, il y a probablement un léger avantage en termes de performances à laisser la promesse remonter en la renvoyant directement - simplement parce que vous n'avez pas à attendre le résultat d'abord, puis à l'envelopper à nouveau avec une autre promesse. Cependant, personne n'a encore parlé d' optimisation des appels de fin .

L'optimisation des appels de queue , ou «appels de queue appropriés» , est une technique que l'interpréteur utilise pour optimiser la pile d'appels. Actuellement, peu d'exécutables le prennent encore en charge - même s'il fait techniquement partie de la norme ES6 - mais il est possible qu'un support soit ajouté à l'avenir, vous pouvez donc vous préparer à cela en écrivant un bon code dans le présent.

En un mot, TCO (ou PTC) optimise la pile d'appels en n'ouvrant pas de nouveau cadre pour une fonction qui est directement renvoyée par une autre fonction. Au lieu de cela, il réutilise le même cadre.

async function delay1Second() {
  return delay(1000);
}

Comme delay()est directement renvoyé par delay1Second(), les environnements d'exécution prenant en charge PTC ouvriront d'abord un cadre pour delay1Second()(la fonction externe), mais au lieu d'ouvrir un autre cadre pour delay()(la fonction interne), il réutilisera simplement le même cadre qui a été ouvert pour la fonction externe. Cela optimise la pile car cela peut empêcher un débordement de pile (hehe) avec de très grandes fonctions récursives, par exemple fibonacci(5e+25). Essentiellement, cela devient une boucle, ce qui est beaucoup plus rapide.

PTC n'est activé que lorsque la fonction interne est directement renvoyée. Il n'est pas utilisé lorsque le résultat de la fonction est modifié avant d'être renvoyé, par exemple, si vous aviez return (delay(1000) || null), ou return await delay(1000).

Mais comme je l'ai dit, la plupart des environnements d'exécution et des navigateurs ne prennent pas encore en charge PTC, donc cela ne fait probablement pas une énorme différence maintenant, mais cela ne pourrait pas nuire à la pérennité de votre code.

Pour en savoir plus, lisez cette question: Node.js: existe-t-il des optimisations pour les appels de queue dans les fonctions asynchrones?

Chharvey
la source
2

C'est une question à laquelle il est difficile de répondre, car cela dépend en pratique de la façon dont votre transpilateur babelrend (probablement ) réellement async/await. Les choses qui sont claires malgré tout:

  • Les deux implémentations doivent se comporter de la même manière, bien que la première implémentation puisse en avoir une de moins Promisedans la chaîne.

  • Surtout si vous supprimez ce qui est inutile await, la deuxième version ne nécessiterait aucun code supplémentaire de la part du transpilateur, contrairement à la première.

Donc, du point de vue des performances du code et du débogage, la deuxième version est préférable, bien que très légèrement, tandis que la première version présente un léger avantage de lisibilité, en ce qu'elle indique clairement qu'elle renvoie une promesse.

Nrabinowitz
la source
Pourquoi les fonctions se comporteraient-elles de la même manière? Le premier renvoie une valeur résolue ( undefined) et le second renvoie a Promise.
Amit
4
@Amit les deux fonctions renvoient une promesse
PitaJ
Ack. C'est pourquoi je ne peux pas supporter async/await- j'ai beaucoup plus de mal à raisonner. @PitaJ est correct, les deux fonctions renvoient une promesse.
nrabinowitz
Et si je devais entourer le corps des deux fonctions asynchrones avec un try-catch? Dans le return promisecas, aucun rejectionne serait attrapé, c'est exact, alors que dans le return await promisecas, ce serait le cas, non?
PitaJ
Les deux renvoient une promesse, mais le premier "promet" une valeur primitive et le second "promet" une promesse. Si vous avez awaitchacun d'eux sur un site d'appel, le résultat sera très différent.
Amit
0

ici je laisse un peu de code pratique pour que vous puissiez comprendre la différence

 let x = async function () {
  return new Promise((res, rej) => {
    setTimeout(async function () {
      console.log("finished 1");
      return await new Promise((resolve, reject) => { // delete the return and you will see the difference
        setTimeout(function () {
          resolve("woo2");
          console.log("finished 2");
        }, 5000);
      });
      res("woo1");
    }, 3000);
  });
};

(async function () {
  var counter = 0;
  const a = setInterval(function () { // counter for every second, this is just to see the precision and understand the code
    if (counter == 7) {
      clearInterval(a);
    }

    console.log(counter);
    counter = counter + 1;
  }, 1000);
  console.time("time1");
  console.log("hello i starting first of all");
  await x();
  console.log("more code...");
  console.timeEnd("time1");
})();

la fonction "x" est juste une fonction asynchrone qu'elle n'a d'autre fucn si elle supprimera le retour elle affichera "plus de code ..."

la variable x est juste une fonction asynchrone qui à son tour a une autre fonction asynchrone, dans le principal du code nous invoquons une attente pour appeler la fonction de la variable x, quand elle se termine, elle suit la séquence du code, ce serait normal pour "async / await", mais à l'intérieur de la fonction x il y a une autre fonction asynchrone, et cela retourne une promesse ou retourne une "promesse", il restera à l'intérieur de la fonction x, oubliant le code principal, c'est-à-dire qu'il n'imprimera pas le "console.log (" plus de code .. "), par contre si nous mettons" wait "il attendra chaque fonction qui se termine et suivra finalement la séquence normale du code principal.

sous le "console.log (" terminé 1 "supprimez le" retour ", vous verrez le comportement.

Carlos Terrazas
la source
1
Bien que ce code puisse résoudre la question, inclure une explication sur comment et pourquoi cela résout le problème aiderait vraiment à améliorer la qualité de votre publication et entraînerait probablement plus de votes à la hausse. N'oubliez pas que vous répondez à la question des lecteurs à l'avenir, pas seulement à la personne qui la pose maintenant. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limites et des hypothèses applicables.
Brian le
0

Voici un exemple dactylographié que vous pouvez exécuter et vous convaincre que vous avez besoin de ce "retour en attente"

async function  test() {
    try {
        return await throwErr();  // this is correct
        // return  throwErr();  // this will prevent inner catch to ever to be reached
    }
    catch (err) {
        console.log("inner catch is reached")
        return
    }
}

const throwErr = async  () => {
    throw("Fake error")
}


void test().then(() => {
    console.log("done")
}).catch(e => {
    console.log("outer catch is reached")
});

David Dehghan
la source
0

Différence notable: le rejet de promesses est traité à différents endroits

  • return somePromisepassera somePromise au site d'appel, et await somePromise à régler sur le site d'appel (s'il y en a). Par conséquent, si somePromise est rejeté, il ne sera pas géré par le bloc catch local, mais par le bloc catch du site d'appel.

async function foo () {
  try {
    return Promise.reject();
  } catch (e) {
    console.log('IN');
  }
}

(async function main () {
  try {
    let a = await foo();
  } catch (e) {
    console.log('OUT');
  }
})();
// 'OUT'

  • return await somePromiseattendra d'abord une promesse de s'installer localement. Par conséquent, la valeur ou l'exception sera d'abord traitée localement. => Le bloc catch local sera exécuté s'il somePromiseest rejeté.

async function foo () {
  try {
    return await Promise.reject();
  } catch (e) {
    console.log('IN');
  }
}

(async function main () {
  try {
    let a = await foo();
  } catch (e) {
    console.log('OUT');
  }
})();
// 'IN'

Raison: return await Promiseattend à la fois localement et à l'extérieur, return Promisen'attend qu'à l'extérieur

Étapes détaillées:

promesse de retour

async function delay1Second() {
  return delay(1000);
}
  1. appel delay1Second();
const result = await delay1Second();
  1. À l'intérieur delay1Second(), la fonction delay(1000)renvoie immédiatement une promesse avec [[PromiseStatus]]: 'pending. Appelons ça delayPromise.
async function delay1Second() {
  return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. Les fonctions asynchrones encapsuleront leur valeur de retour dans Promise.resolve()( Source ). Parce que delay1Secondc'est une fonction asynchrone, nous avons:
const result = await Promise.resolve(delayPromise); 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. Promise.resolve(delayPromise)renvoie delayPromisesans rien faire car l'entrée est déjà une promesse (voir MDN Promise.resolve ):
const result = await delayPromise; 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. awaitattend que le delayPromisesoit réglé.
  • IF delayPromiseest rempli avec PromiseValue = 1:
const result = 1; 
  • ELSE delayPromiseest rejeté:
// jump to catch block if there is any

retour attendre promesse

async function delay1Second() {
  return await delay(1000);
}
  1. appel delay1Second();
const result = await delay1Second();
  1. À l'intérieur delay1Second(), la fonction delay(1000)renvoie immédiatement une promesse avec [[PromiseStatus]]: 'pending. Appelons ça delayPromise.
async function delay1Second() {
  return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. L'attente locale attendra d' delayPromiseêtre réglée.
  • Cas 1 : delayPromiseest rempli avec PromiseValue = 1:
async function delay1Second() {
  return 1;
}
const result = await Promise.resolve(1); // let's call it "newPromise"
const result = await newPromise; 
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: 1
const result = 1; 
  • Cas 2 : delayPromiseest rejeté:
// jump to catch block inside `delay1Second` if there is any
// let's say a value -1 is returned in the end
const result = await Promise.resolve(-1); // call it newPromise
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: -1
const result = -1;

Glossaire:

  • Régler: Promise.[[PromiseStatus]]passe de pendingà resolvedourejected
Rag-time
la source