Unterschied zwischen "Rückkehr warten Versprechen" und "Rückkehr Versprechen"

105

Gibt es bei den folgenden Codebeispielen Unterschiede im Verhalten, und wenn ja, welche Unterschiede gibt es?

return await promise

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

return promise

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

So wie ich es verstehe, würde der erste eine Fehlerbehandlung innerhalb der asynchronen Funktion haben, und Fehler würden aus dem Versprechen der asynchronen Funktion heraussprudeln. Die zweite würde jedoch einen Tick weniger erfordern. Ist das richtig?

Dieses Snippet ist nur eine übliche Funktion, um ein Versprechen als Referenz zurückzugeben.

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}
PitaJ
quelle
3
Ja, ich habe meine Frage bearbeitet, weil Sie meine Bedeutung falsch verstanden haben und sie nicht wirklich beantwortet hat, was ich mich gefragt habe.
PitaJ
1
@PitaJ: Ich glaube, Sie wollten das asyncaus Ihrem zweiten ( return promise) Beispiel entfernen .
Stephen Cleary
1
@PitaJ: In diesem Fall würde Ihr zweites Beispiel ein Versprechen zurückgeben, das mit einem Versprechen gelöst wird. Eher komisch.
Stephen Cleary
5
jakearchibald.com/2017/await-vs-return-vs-return-await ist ein schöner Artikel, der die Unterschiede zusammenfasst
sanchit
2
@StephenCleary, ich bin darauf gestoßen und habe zuerst genau dasselbe gedacht, ein Versprechen, das mit einem Versprechen gelöst wird, macht hier keinen Sinn. Aber wie es sich dreht, promise.then(() => nestedPromise)würde sich abflachen und dem "folgen" nestedPromise. Interessant, wie es sich von verschachtelten Aufgaben in C # unterscheidet, bei denen wir es tun müssten Unwrap. Nebenbei bemerkt, es scheint, dass await somePromise Aufrufe Promise.resolve(somePromise).thennicht nur somePromise.thenmit einigen interessanten semantischen Unterschieden.
Noseratio

Antworten:

151

Meistens gibt es keinen beobachtbaren Unterschied zwischen returnund return await. Beide Versionen von delay1Secondhaben genau das gleiche beobachtbare Verhalten (abhängig von der Implementierung verwendet die return awaitVersion jedoch möglicherweise etwas mehr Speicher, da Promisemöglicherweise ein Zwischenobjekt erstellt wird).

Doch wie @PitaJ darauf hingewiesen, gibt es einen Fall , in dem es ein Unterschied ist: Wenn die returnoder return awaitin einem verschachtelt ist try- catchBlock. Betrachten Sie dieses Beispiel

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!'
  }
}

In der ersten Version wartet die asynchrone Funktion auf das abgelehnte Versprechen, bevor sie das Ergebnis zurückgibt, wodurch die Ablehnung in eine Ausnahme umgewandelt und die catchKlausel erreicht wird. Die Funktion gibt somit ein Versprechen zurück, das in die Zeichenfolge "Gespeichert!" aufgelöst wird.

Die zweite Version der Funktion gibt das abgelehnte Versprechen jedoch direkt zurück, ohne es innerhalb der asynchronen Funktion abzuwarten , was bedeutet, dass dies der catchFall ist nicht aufgerufen wird und der Aufrufer stattdessen die Ablehnung erhält.

Denis Washington
quelle
Erwähnen Sie vielleicht auch, dass der Stack-Trace anders wäre (auch ohne Try / Catch)? Ich denke, das ist das Problem, auf das die Leute in diesem Beispiel am häufigsten
stoßen
Ich habe in einem Szenario festgestellt, dass return new Promise(function(resolve, reject) { })innerhalb einer for...ofSchleife und dann aufrufenresolve() innerhalb der Schleife nach a pipe()die Programmausführung nicht pausiert, bis die Pipe abgeschlossen ist, wie gewünscht, jedoch unter Verwendung await new Promise(...). Ist letztere überhaupt eine gültige / korrekte Syntax? ist es "Abkürzung" für return await new Promise(...)? Können Sie mir helfen zu verstehen, warum Letzteres funktioniert und Ersteres nicht? für Kontext, ist das Szenario , in solution 02der diese Antwort
user1063287
10

Wie in anderen Antworten erwähnt, gibt es wahrscheinlich einen leichten Leistungsvorteil, wenn das Versprechen durch direkte Rückgabe in die Luft sprudelt - einfach, weil Sie nicht zuerst auf das Ergebnis warten und es dann erneut mit einem anderen Versprechen abschließen müssen. Bisher hat jedoch noch niemand über die Optimierung von Tail Calls gesprochen .

Die Tail-Call-Optimierung oder „richtige Tail-Calls“ ist eine Technik, mit der der Interpreter den Call-Stack optimiert. Derzeit unterstützen es noch nicht viele Laufzeiten - obwohl es technisch Teil des ES6-Standards ist -, aber es ist möglich, dass in Zukunft Unterstützung hinzugefügt wird, sodass Sie sich darauf vorbereiten können, indem Sie in der Gegenwart guten Code schreiben.

Kurz gesagt, TCO (oder PTC) optimiert den Aufrufstapel, indem kein neuer Frame für eine Funktion geöffnet wird, die direkt von einer anderen Funktion zurückgegeben wird. Stattdessen wird derselbe Frame wiederverwendet.

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

Da PTC, das PTC unterstützt, delay()direkt von zurückgegeben wird delay1Second(), wird zuerst ein Frame für delay1Second()(die äußere Funktion) geöffnet. Statt jedoch einen anderen Frame für delay()(die innere Funktion) zu öffnen , wird nur der Frame wiederverwendet, der für die äußere Funktion geöffnet wurde. Dies optimiert den Stapel, da es einen Stapelüberlauf (hehe) mit sehr großen rekursiven Funktionen verhindern kann, z fibonacci(5e+25). Im Wesentlichen wird es eine Schleife, die viel schneller ist.

PTC ist nur aktiviert, wenn die innere Funktion direkt zurückgegeben wird. Es wird nicht verwendet, wenn das Ergebnis der Funktion geändert wird, bevor es zurückgegeben wird, z. B. wenn Sie return (delay(1000) || null)oder hattenreturn await delay(1000) .

Aber wie gesagt, die meisten Laufzeiten und Browser unterstützen PTC noch nicht, daher macht es jetzt wahrscheinlich keinen großen Unterschied, aber es könnte nicht schaden, Ihren Code zukunftssicher zu machen.

Lesen Sie mehr in dieser Frage: Node.js: Gibt es Optimierungen für Tail-Aufrufe in asynchronen Funktionen?

chharvey
quelle
2

Diese Frage ist schwer zu beantworten, da sie in der Praxis davon abhängt, wie Ihr Transpiler (wahrscheinlich babel) tatsächlich rendert async/await. Die Dinge, die unabhängig davon klar sind:

  • Beide Implementierungen sollten sich gleich verhalten, obwohl die erste Implementierung möglicherweise eine weniger Promisein der Kette hat.

  • Insbesondere wenn Sie das Unnötige fallen lassen await, würde die zweite Version keinen zusätzlichen Code vom Transpiler erfordern, während die erste dies tut.

Aus Sicht der Codeleistung und des Debuggens ist die zweite Version vorzuziehen, wenn auch nur geringfügig. Die erste Version bietet jedoch einen leichten Lesbarkeitsvorteil, da sie eindeutig anzeigt, dass sie ein Versprechen zurückgibt.

nrabinowitz
quelle
Warum sollten sich die Funktionen gleich verhalten? Der erste gibt einen aufgelösten Wert ( undefined) zurück und der zweite gibt a zurück Promise.
Amit
4
@ Lassen Sie beide Funktionen ein Versprechen zurückgeben
PitaJ
Bestätigen. Deshalb kann ich nicht stehen async/await- es fällt mir viel schwerer, darüber nachzudenken. @PitaJ ist korrekt, beide Funktionen geben ein Versprechen zurück.
Nrabinowitz
Was wäre, wenn ich den Körper beider asynchroner Funktionen mit einem umgeben würde try-catch? In dem return promiseFall rejectionwürde keiner gefangen werden, richtig, während es in dem return await promiseFall wäre, richtig?
PitaJ
Beide geben ein Versprechen zurück, aber das erste "verspricht" einen primitiven Wert und das zweite "verspricht" ein Versprechen. Wenn Sie awaitdiese jeweils an einer Anrufstelle verwenden, ist das Ergebnis sehr unterschiedlich.
Amit
0

Hier lasse ich einen Code praktisch, damit Sie den Unterschied verstehen können

 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");
})();

Die Funktion "x" ist nur eine asynchrone Funktion als eine andere Funktion, wenn die Rückgabe gelöscht wird. "Mehr Code ..."

Die Variable x ist nur eine asynchrone Funktion, die wiederum eine andere asynchrone Funktion hat. Im Hauptteil des Codes rufen wir eine Wartezeit auf, um die Funktion der Variablen x aufzurufen. Wenn sie abgeschlossen ist, folgt sie der normalen Reihenfolge des Codes für "async / await", aber innerhalb der x-Funktion gibt es eine andere asynchrone Funktion, die ein Versprechen zurückgibt oder ein "Versprechen" zurückgibt. Es bleibt in der x-Funktion und vergisst den Hauptcode, das heißt, es wird das nicht gedruckt "console.log (" more code .. "), wenn wir dagegen" await "setzen, wartet es auf jede Funktion, die abgeschlossen ist und schließlich der normalen Reihenfolge des Hauptcodes folgt.

Unter dem "console.log" ("fertig 1" löschen Sie die "Rückgabe", Sie werden das Verhalten sehen.

Carlos Terrazas
quelle
1
Während dieser Code die Frage lösen kann, einschließlich einer Erklärung, wie und warum dies das Problem löst, würde dies wirklich dazu beitragen, die Qualität Ihres Beitrags zu verbessern, und wahrscheinlich zu mehr Up-Votes führen. Denken Sie daran, dass Sie in Zukunft die Frage für die Leser beantworten, nicht nur für die Person, die jetzt fragt. Bitte bearbeiten Sie Ihre Antwort, um Erklärungen hinzuzufügen und anzugeben, welche Einschränkungen und Annahmen gelten.
Brian
0

Hier ist ein Typoskript-Beispiel, das Sie ausführen und sich davon überzeugen können, dass Sie diese "Rückkehr warten" benötigen.

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
quelle
0

Bemerkenswerter Unterschied: Die Ablehnung von Versprechungen wird an verschiedenen Stellen behandelt

  • return somePromisevergehen somePromise an die Aufrufstelle und await somePromise zu settle bei Aufrufort (falls vorhanden). Wenn somePromise abgelehnt wird, wird es daher nicht vom lokalen catch-Block, sondern vom catch-Block der Call-Site verarbeitet.

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 somePromiseIch werde zuerst auf SomePromise warten, um mich vor Ort niederzulassen. Daher wird der Wert oder die Ausnahme zuerst lokal behandelt. => Lokaler Catch-Block wird ausgeführt, wenn er somePromiseabgelehnt wird.

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'

Grund: return await Promiseerwartet sowohl vor Ort als auch außerhalb,return Promise wartet nur draußen

Detaillierte Schritte:

Versprechen zurück

async function delay1Second() {
  return delay(1000);
}
  1. anrufen delay1Second();
const result = await delay1Second();
  1. Im Inneren delay1Second()gibt function delay(1000)sofort ein Versprechen mit zurück [[PromiseStatus]]: 'pending. Nennen wir es delayPromise.
async function delay1Second() {
  return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. Asynchrone Funktionen schließen ihren Rückgabewert in Promise.resolve()( Quelle ) ein. Da delay1Secondes sich um eine asynchrone Funktion handelt, haben wir:
const result = await Promise.resolve(delayPromise); 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. Promise.resolve(delayPromise)kehrt zurück, delayPromiseohne etwas zu tun, da die Eingabe bereits ein Versprechen ist (siehe MDN Promise.resolve ):
const result = await delayPromise; 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. awaitwartet bis das delayPromiseerledigt ist.
  • WENN delayPromisemit PromiseValue = 1 erfüllt ist:
const result = 1; 
  • ELSE delayPromisewird abgelehnt:
// jump to catch block if there is any

Rückkehr warten Versprechen

async function delay1Second() {
  return await delay(1000);
}
  1. anrufen delay1Second();
const result = await delay1Second();
  1. Im Inneren delay1Second()gibt function delay(1000)sofort ein Versprechen mit zurück [[PromiseStatus]]: 'pending. Nennen wir es delayPromise.
async function delay1Second() {
  return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. Das lokale Warten wartet, bis es delayPromiseerledigt ist.
  • Fall 1 : delayPromisewird mit PromiseValue = 1 erfüllt:
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; 
  • Fall 2 : delayPromisewird abgelehnt:
// 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;

Glossar:

  • Settle: Promise.[[PromiseStatus]]wechselt von pendingzu resolvedoderrejected
Ragtime
quelle