Differenza tra "return await promise" e "return promise"


108

Dati gli esempi di codice seguenti, c'è qualche differenza nel comportamento e, in caso affermativo, quali sono queste differenze?

return await promise

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

return promise

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

A quanto ho capito, il primo avrebbe la gestione degli errori all'interno della funzione asincrona e gli errori sarebbero fuoriusciti dalla promessa della funzione asincrona. Tuttavia, il secondo richiederebbe un segno di spunta in meno. È corretto?

Questo frammento è solo una funzione comune per restituire una promessa come riferimento.

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

3
Sì, ho modificato la mia domanda perché hai frainteso il mio significato e non ha davvero risposto a quello che mi chiedevo.
PitaJ

1
@ PitaJ: credo che volessi rimuovere il asyncdal tuo secondo ( return promise) campione.
Stephen Cleary

1
@PitaJ: in tal caso, il tuo secondo esempio restituirebbe una promessa che si risolve con una promessa. Piuttosto strano.
Stephen Cleary

5
jakearchibald.com/2017/await-vs-return-vs-return-await è un bell'articolo che riassume le differenze
sanchit

2
@StephenCleary, mi sono imbattuto in questo e prima ho pensato esattamente la stessa cosa, una promessa che si risolve con una promessa non ha senso qui. Ma come si trasforma, promise.then(() => nestedPromise)appiattirebbe e "seguirebbe" il file nestedPromise. Interessante come è diverso dalle attività annidate in C # dove dovremmo Unwrapfarlo. In una nota a margine, sembra che await somePromise richiami Promise.resolve(somePromise).then, piuttosto che solo somePromise.then, con alcune interessanti differenze semantiche.
noseratio

Risposte:


155

La maggior parte delle volte non c'è alcuna differenza osservabile tra returne return await. Entrambe le versioni di delay1Secondhanno lo stesso identico comportamento osservabile (ma a seconda dell'implementazione, la return awaitversione potrebbe utilizzare un po 'più di memoria perché Promisepotrebbe essere creato un oggetto intermedio ).

Tuttavia, come @PitaJ sottolineato, c'è un caso in cui esista una differenza: se la returno return awaitè annidato in un try- catchblocco. Considera questo esempio

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

Nella prima versione, la funzione async attende la promessa rifiutata prima di restituirne il risultato, il che fa sì che il rifiuto venga trasformato in un'eccezione e catchche venga raggiunta la clausola; la funzione restituirà così una promessa che si risolverà nella stringa "Salvato!".

La seconda versione della funzione, tuttavia, restituisce direttamente la promessa rifiutata senza aspettarla all'interno della funzione asincrona , il che significa che il catchcaso non viene chiamato e il chiamante ottiene invece il rifiuto.


Forse anche menzionare che la traccia dello stack sarebbe diversa (anche senza un try / catch)? Penso che questo sia il problema che le persone incontrano più spesso in questo esempio:]
Benjamin Gruenbaum

Ho scoperto in uno scenario, che l'utilizzo return new Promise(function(resolve, reject) { })all'interno di un for...ofciclo e quindi la chiamata resolve()all'interno del ciclo dopo a pipe()non interrompe l'esecuzione del programma fino al completamento del pipe, come desiderato, tuttavia l'utilizzo lo await new Promise(...)fa. quest'ultima è anche una sintassi valida / corretta? è "scorciatoia" per return await new Promise(...)? potresti aiutarmi a capire perché quest'ultimo funziona e il primo no? per il contesto, lo scenario è in solution 02di questa risposta
user1063287

12

Come accennato in altre risposte, è probabile che vi sia un leggero vantaggio in termini di prestazioni quando si lascia che la promessa sgorghi restituendola direttamente, semplicemente perché non è necessario attendere prima il risultato e poi avvolgerlo di nuovo con un'altra promessa. Tuttavia, nessuno ha ancora parlato di ottimizzazione delle chiamate di coda .

L'ottimizzazione delle chiamate di coda , o "chiamate di coda appropriate" , è una tecnica che l'interprete utilizza per ottimizzare lo stack di chiamate. Attualmente, non molti runtime lo supportano ancora , anche se tecnicamente fa parte dello standard ES6 , ma è possibile che il supporto venga aggiunto in futuro, quindi puoi prepararti scrivendo un buon codice nel presente.

In poche parole, TCO (o PTC) ottimizza lo stack di chiamate non aprendo un nuovo frame per una funzione che viene restituita direttamente da un'altra funzione. Invece, riutilizza lo stesso frame.

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

Poiché delay()viene restituito direttamente da delay1Second(), i runtime che supportano PTC apriranno prima un frame per delay1Second()(la funzione esterna), ma poi invece di aprire un altro frame per delay()(la funzione interna), riutilizzerà semplicemente lo stesso frame che è stato aperto per la funzione esterna. Questo ottimizza lo stack perché può impedire un overflow dello stack (hehe) con funzioni ricorsive molto grandi, ad esempio fibonacci(5e+25). Essenzialmente diventa un loop, che è molto più veloce.

PTC è abilitato solo quando viene restituita direttamente la funzione interna . Non viene utilizzato quando il risultato della funzione viene modificato prima di essere restituito, ad esempio, se si aveva return (delay(1000) || null), o return await delay(1000).

Ma come ho detto, la maggior parte dei runtime e dei browser non supporta ancora PTC, quindi probabilmente non fa una grande differenza ora, ma non potrebbe far male a rendere il tuo codice a prova di futuro.

Leggi di più in questa domanda: Node.js: ci sono ottimizzazioni per le chiamate tail nelle funzioni asincrone?


2

Questa è una domanda difficile a cui rispondere, perché dipende in pratica da come il tuo transpiler (probabilmente babel) esegue effettivamente il rendering async/await. Le cose che sono chiare a prescindere:

  • Entrambe le implementazioni dovrebbero comportarsi allo stesso modo, sebbene la prima implementazione possa averne una in meno Promisenella catena.

  • Soprattutto se si elimina il non necessario await, la seconda versione non richiederebbe alcun codice aggiuntivo dal transpiler, mentre la prima lo fa.

Quindi, dal punto di vista delle prestazioni del codice e del debug, la seconda versione è preferibile, anche se solo leggermente, mentre la prima versione ha un leggero vantaggio di leggibilità, in quanto indica chiaramente che restituisce una promessa.


Perché le funzioni si comportano allo stesso modo? Il primo restituisce un valore risolto ( undefined) e il secondo restituisce a Promise.
Amit

4
@Amit entrambe le funzioni restituiscono una promessa
PitaJ

Ack. Questo è il motivo per cui non sopporto async/await: trovo molto più difficile ragionare. @PitaJ è corretto, entrambe le funzioni restituiscono una promessa.
nrabinowitz

E se circondassi il corpo di entrambe le funzioni asincrone con un try-catch? Nel return promisecaso, nessuno rejectionverrebbe colto, corretto, mentre, nel return await promisecaso, lo sarebbe, giusto?
PitaJ

Entrambi restituiscono una Promessa, ma la prima "promette" un valore primitivo e la seconda "promette" una Promessa. Se awaitognuno di questi in un sito di chiamata, il risultato sarà molto diverso.
Amit

1

Notevole differenza: il rifiuto della promessa viene gestito in luoghi diversi

  • return somePromisepasserà alcune promesse al sito della chiamata e await alcune promesse di stabilirsi nel sito della chiamata (se presente). Pertanto, se somePromise viene rifiutato, non verrà gestito dal blocco catch locale, ma dal blocco catch del sito della chiamata.

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 somePromiseprima attenderà qualche promessa per stabilirsi localmente. Pertanto, il valore o l'eccezione verrà prima gestito localmente. => Il blocco catch locale verrà eseguito se somePromiserifiutato.

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'

Motivo: return await Promiseattende sia localmente che fuori, return Promiseaspetta solo fuori

Passaggi dettagliati:

Promessa di ritorno

async function delay1Second() {
  return delay(1000);
}
  1. chiamare delay1Second();
const result = await delay1Second();
  1. All'interno delay1Second(), la funzione delay(1000)restituisce immediatamente una promessa con [[PromiseStatus]]: 'pending. Chiamiamolo delayPromise.
async function delay1Second() {
  return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. Le funzioni asincrone avvolgeranno il loro valore restituito all'interno di Promise.resolve()( Sorgente ). Poiché delay1Secondè una funzione asincrona, abbiamo:
const result = await Promise.resolve(delayPromise); 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. Promise.resolve(delayPromise)ritorna delayPromisesenza fare nulla perché l'input è già una promessa (vedi MDN Promise.resolve ):
const result = await delayPromise; 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. awaitattende fino a quando non delayPromiseviene risolto.
  • SE delayPromiseè soddisfatto con PromiseValue = 1:
const result = 1; 
  • ELSE delayPromiseè rifiutato:
// jump to catch block if there is any

ritorno in attesa della promessa

async function delay1Second() {
  return await delay(1000);
}
  1. chiamare delay1Second();
const result = await delay1Second();
  1. All'interno delay1Second(), la funzione delay(1000)restituisce immediatamente una promessa con [[PromiseStatus]]: 'pending. Chiamiamolo delayPromise.
async function delay1Second() {
  return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. L'attesa locale aspetterà fino a quando non delayPromiseverrà sistemata.
  • Caso 1 : delayPromiseè soddisfatto con 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; 
  • Caso 2 : delayPromiseviene rifiutato:
// 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;

Glossario:

  • Settle: Promise.[[PromiseStatus]]cambia da pendinga resolvedorejected

0

qui lascio un po 'di codice pratico perché tu possa capirne la differenza

 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 funzione "x" è semplicemente una funzione asincrona rispetto a quella che ha altri fucn se cancellerà la restituzione stamperà "più codice ..."

la variabile x è solo una funzione asincrona che a sua volta ha un'altra funzione asincrona, nel principale del codice invochiamo un'attesa per chiamare la funzione della variabile x, quando la completa segue la sequenza del codice, che sarebbe normale per "async / await", ma all'interno della funzione x c'è un'altra funzione asincrona, e questa restituisce una promessa o restituisce una "promessa" rimarrà all'interno della funzione x, dimenticando il codice principale, cioè non stamperà il "console.log (" more code .. "), invece se mettiamo" await "attenderà ogni funzione che completa e infine segue la normale sequenza del codice principale.

sotto "console.log (" finito 1 "elimina il" ritorno ", vedrai il comportamento.


1
Sebbene questo codice possa risolvere la domanda, inclusa una spiegazione di come e perché questo risolve il problema aiuterebbe davvero a migliorare la qualità del tuo post e probabilmente si tradurrebbe in più voti positivi. Ricorda che stai rispondendo alla domanda per i lettori in futuro, non solo alla persona che chiede ora. Si prega di modificare la risposta per aggiungere spiegazioni e dare un'indicazione di ciò si applicano le limitazioni e le assunzioni.
Brian

0

Ecco un esempio di dattiloscritto che puoi eseguire e convincerti di aver bisogno del "ritorno in attesa"

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.