Come rifiutare nella sintassi asincrona / wait?


283

Come posso rifiutare una promessa restituita da una funzione asincrona / wait?

ad es. in origine

foo(id: string): Promise<A> {
  return new Promise((resolve, reject) => {
    someAsyncPromise().then((value)=>resolve(200)).catch((err)=>reject(400))
  });
}

Traduci in asincrono / attendi

async foo(id: string): Promise<A> {
  try{
    await someAsyncPromise();
    return 200;
  } catch(error) {//here goes if someAsyncPromise() rejected}
    return 400; //this will result in a resolved promise.
  });
}

Quindi, come potrei rifiutare correttamente questa promessa in questo caso?


20
Evita l' Promiseantipasto del costruttore ! Anche il primo frammento avrebbe dovuto essere scrittofoo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
Bergi il

10
Penso che sarebbe utile tradurre il codice in questa domanda in vaniglia JS, poiché la domanda non ha nulla a che fare con TypeScript. Se lo facessi, tale modifica verrebbe probabilmente accettata?
Jacob Ford,

Risposte:


328

La tua scommessa migliore è throwun Errorwrapping del valore, che si traduce in una promessa rifiutata con un Errorwrapping del valore:

} catch (error) {
    throw new Error(400);
}

Puoi anche solo throwil valore, ma non ci sono informazioni sullo stack stack:

} catch (error) {
    throw 400;
}

In alternativa, restituisci una promessa rifiutata con un Errorvalore a capo, ma non è idiomatico:

} catch (error) {
    return Promise.reject(new Error(400));
}

(O solo return Promise.reject(400);, ma di nuovo, quindi non ci sono informazioni sul contesto.)

(Nel tuo caso, mentre stai usando TypeScripte fooil valore retrn è Promise<A>, dovresti usare return Promise.reject<A>(400 /*or error*/);)

In una situazione async/ await, l'ultimo è probabilmente un po 'un errore di corrispondenza semantica, ma funziona.

Se si lancia un Error, che gioca bene con tutto ciò che consuma il foorisultato del tuo awaitsintassi:

try {
    await foo();
} catch (error) {
    // Here, `error` would be an `Error` (with stack trace, etc.).
    // Whereas if you used `throw 400`, it would just be `400`.
}

12
E poiché async / waitit riguarda il ripristino del flusso asincrono per sincronizzare la sintassi, throwè meglio di Promise.reject()IMO. Se throw 400è una domanda diversa. Nel PO sta respingendo 400, e possiamo sostenere che dovrebbe Errorinvece rifiutare un .
sindacale

2
Sì, tuttavia, se la tua catena di codici utilizza davvero async / waitit, allora ..... sarai difficile digitare qui, fammi demo come risposta
sindacale

1
c'è qualche motivo per cui vorresti lanciare un nuovo errore rispetto all'errore che ti è stato dato nel blocco catch?
Adrian M,

1
@sebastian - Non so cosa intendi lì. In asyncfunzioni, non c'è resolveo rejectfunzione. C'è returne throw, quali sono i modi idiomatici per risolvere e rifiutare la asyncpromessa della funzione.
TJ Crowder,

1
@ Jan-PhilipGehrcke - È possibile , ma non lo faccio mai. Sta creando un'istanza, lo newrende esplicito. Nota anche che non puoi lasciarlo fuori se hai una Errorsottoclasse ( class MyError extends Error), quindi ...
TJ Crowder,

146

Probabilmente dovresti anche menzionare che puoi semplicemente concatenare una catch()funzione dopo la chiamata dell'operazione asincrona perché sotto il cofano viene ancora restituita una promessa.

await foo().catch(error => console.log(error));

In questo modo puoi evitare la try/catchsintassi se non ti piace.


1
Quindi, se voglio rifiutare la mia asyncfunzione, lancio un'eccezione e poi la prendo bene con, .catch()proprio come se fossi tornato Promise.rejecto chiamato reject. Mi piace!
icl7126,

7
Non capisco perché questa dovrebbe essere la risposta accettata. Non solo la risposta accettata è più pulita, ma gestisce anche tutti i possibili awaitguasti in una routine. A meno che non siano necessari casi molto specifici per ciascuno di essi await, non vedo perché vorresti catturarli in questo modo. Solo io un'umile opinione.
edgaralienfoe,

1
@jablesauce per il mio caso d'uso, non solo dovevo rilevare ogni awaiterrore separatamente, ma dovevo anche lavorare con un framework basato su Promise che respingeva le promesse di errore.
Reuven Karasik,

Non ha funzionato per me. Non sembra andare in blocco se url non riesce. [response] = await oauthGet ( ${host}/user/permissions/repositories_wrong_url/, accessToken, accessTokenSecret) .catch (err => {logger.error ('Impossibile recuperare le autorizzazioni del repository', err); callback (err);})
sn.anurag

1
awaitparola chiave non necessaria qui.
Ashish Rawat,

12

È possibile creare una funzione wrapper che accetta una promessa e restituisce un array con dati se nessun errore e l'errore se si è verificato un errore.

function safePromise(promise) {
  return promise.then(data => [ data ]).catch(error => [ null, error ]);
}

Usalo in questo modo in ES7 e in una funzione asincrona :

async function checkItem() {
  const [ item, error ] = await safePromise(getItem(id));
  if (error) { return null; } // handle error and return
  return item; // no error so safe to use item
}

1
Sembra un tentativo di avere la bella sintassi Go ma senza molta eleganza. Trovo che il codice che lo utilizza sia offuscato quanto basta per risucchiare il valore dalla soluzione.
Kim,

8

Un modo migliore per scrivere la funzione asincrona sarebbe restituire una Promessa in sospeso dall'inizio e quindi gestire sia i rifiuti che le risoluzioni all'interno del callback della promessa, piuttosto che sputare una promessa rifiutata per errore. Esempio:

async foo(id: string): Promise<A> {
    return new Promise(function(resolve, reject) {
        // execute some code here
        if (success) { // let's say this is a boolean value from line above
            return resolve(success);
        } else {
            return reject(error); // this can be anything, preferably an Error object to catch the stacktrace from this function
        }
    });
}

Quindi basta concatenare i metodi sulla promessa restituita:

async function bar () {
    try {
        var result = await foo("someID")
        // use the result here
    } catch (error) {
        // handle error here
    }
}

bar()

Fonte: questo tutorial:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise


5
La domanda posta specificamente sull'uso di async / await. Non usare le promesse
Mak

Questa risposta non intendeva essere la risposta corretta definitiva. Questa è stata una risposta di supporto alle altre risposte fornite sopra. Lo avrei messo come commento ma dato che ho il codice, il campo di risposta è un posto migliore.
OzzyTheGiant,

Grazie per il chiarimento. Mostrare come creare una funzione asincrona è sicuramente utile. L'aggiornamento del secondo blocco di codice da utilizzare wait sarà molto più pertinente e utile. Saluti
Mak

Ho modificato la tua risposta per aggiornarla. Fammi sapere se mi sono perso qualcosa
Mak

4

Ho un suggerimento per gestire correttamente gli scarti in un nuovo approccio, senza avere più blocchi try-catch.

import to from './to';

async foo(id: string): Promise<A> {
    let err, result;
    [err, result] = await to(someAsyncPromise()); // notice the to() here
    if (err) {
        return 400;
    }
    return 200;
}

Da dove la funzione to.ts deve essere importata da:

export default function to(promise: Promise<any>): Promise<any> {
    return promise.then(data => {
        return [null, data];
    }).catch(err => [err]);
}

I crediti vanno a Dima Grossman nel seguente link .


1
Uso questa costruzione quasi esclusivamente (molto più pulita) e c'è un modulo 'to' che è in circolazione da un po 'di tempo npmjs.com/package/await-to-js . Non è necessaria la dichiarazione separata, basta che sia posta davanti al compito decostruito. Inoltre può fare solo let [err]=se verifica solo gli errori.
DKebler

3

Questa non è una risposta rispetto a quella di @TJ Crowder. Solo un commento che risponde al commento "E in realtà, se l'eccezione verrà convertita in rifiuto, non sono sicuro di essere effettivamente infastidito se si tratta di un errore. I miei motivi per lanciare solo l'errore probabilmente non si applicano. "

se il tuo codice utilizza async/ await, è comunque buona norma rifiutare con un Errorinvece di 400:

try {
  await foo('a');
}
catch (e) {
  // you would still want `e` to be an `Error` instead of `400`
}

3

So che questa è una vecchia domanda, ma mi sono appena imbattuto nel thread e sembra che qui ci sia una conflazione tra errori e rifiuto che corre contro (in molti casi, almeno) il consiglio spesso ripetuto di non usare la gestione delle eccezioni per gestire i casi previsti. Per illustrare: se un metodo asincrono sta tentando di autenticare un utente e l'autenticazione fallisce, si tratta di un rifiuto (uno dei due casi previsti) e non di un errore (ad esempio, se l'API di autenticazione non era disponibile).

Per essere sicuro che non stavo solo dividendo i capelli, ho eseguito un test delle prestazioni di tre diversi approcci, usando questo codice:

const iterations = 100000;

function getSwitch() {
  return Math.round(Math.random()) === 1;
}

function doSomething(value) {
  return 'something done to ' + value.toString();
}

let processWithThrow = function () {
  if (getSwitch()) {
    throw new Error('foo');
  }
};

let processWithReturn = function () {
  if (getSwitch()) {
    return new Error('bar');
  } else {
    return {}
  }
};

let processWithCustomObject = function () {
  if (getSwitch()) {
    return {type: 'rejection', message: 'quux'};
  } else {
    return {type: 'usable response', value: 'fnord'};
  }
};

function testTryCatch(limit) {
  for (let i = 0; i < limit; i++) {
    try {
      processWithThrow();
    } catch (e) {
      const dummyValue = doSomething(e);
    }
  }
}

function testReturnError(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithReturn();
    if (returnValue instanceof Error) {
      const dummyValue = doSomething(returnValue);
    }
  }
}

function testCustomObject(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithCustomObject();
    if (returnValue.type === 'rejection') {
      const dummyValue = doSomething(returnValue);
    }
  }
}

let start, end;
start = new Date();
testTryCatch(iterations);
end = new Date();
const interval_1 = end - start;
start = new Date();
testReturnError(iterations);
end = new Date();
const interval_2 = end - start;
start = new Date();
testCustomObject(iterations);
end = new Date();
const interval_3 = end - start;

console.log(`with try/catch: ${interval_1}ms; with returned Error: ${interval_2}ms; with custom object: ${interval_3}ms`);

Alcune delle cose che ci sono sono incluse a causa della mia incertezza riguardo all'interprete Javascript (mi piace solo scendere in una tana di coniglio alla volta); per esempio, ho incluso la doSomethingfunzione e assegnato il suo ritorno dummyValueper garantire che i blocchi condizionali non fossero ottimizzati.

I miei risultati sono stati:

with try/catch: 507ms; with returned Error: 260ms; with custom object: 5ms

So che ci sono molti casi in cui non vale la pena cacciare piccole ottimizzazioni, ma nei sistemi su larga scala queste cose possono fare una grande differenza cumulativa, e questo è un confronto piuttosto netto.

Quindi ... mentre penso che l'approccio della risposta accettata sia valido nei casi in cui ti aspetti di dover gestire errori imprevedibili all'interno di una funzione asincrona, nei casi in cui un rifiuto significhi semplicemente "dovrai andare con il piano B (o C o D ...) "Penso che la mia preferenza sarebbe quella di rifiutare usando un oggetto di risposta personalizzato.


2
Inoltre, ricorda che non è necessario stressarsi a gestire gli errori imprevisti all'interno di una funzione asincrona se la chiamata a tale funzione si trova all'interno di un blocco try / catch nell'ambito compreso poiché - diversamente da Promises - le funzioni asincrone eseguono il bubble degli errori generati nella racchiudendo l'ambito, dove vengono gestiti proprio come errori locali a tale ambito. Questo è uno dei principali vantaggi di async / wait!
RiqueW

I microbenchmark sono il diavolo. Guarda più da vicino i numeri. Devi fare qualcosa di 1000x per notare una differenza di 1ms qui. Sì, l'aggiunta di lancio / cattura disattiverà la funzione. Ma a) se stai aspettando qualcosa di asincrono, è probabile che occorrano più ordini di grandezza impiegando più di 0,0005 M in background. b) devi farlo 1000x per fare una differenza di 1ms qui.
Jamie Pate,
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.