¿Cómo rechazar en sintaxis asíncrona / espera?

283

¿Cómo puedo rechazar una promesa devuelta por una función asíncrona / espera?

por ejemplo, originalmente

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

Traducir a asíncrono / esperar

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

Entonces, ¿cómo podría rechazar adecuadamente esta promesa en este caso?

Fénix
fuente
20
¡Evita el Promiseconstructor antipatrón ! Incluso el primer fragmento debería haberse escritofoo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
Bergi
10
Creo que sería útil traducir el código de esta pregunta a Vanilla JS, ya que la pregunta no tiene nada que ver con TypeScript. Si lo hiciera, ¿esa edición probablemente sería aceptada?
Jacob Ford

Respuestas:

328

Su mejor apuesta es throwuna Errorenvoltura del valor, lo que resulta en una promesa rechazada con una Errorenvoltura del valor:

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

También puede solo throwel valor, pero no hay información de seguimiento de la pila:

} catch (error) {
    throw 400;
}

Alternativamente, devuelva una promesa rechazada con una Errorenvoltura del valor, pero no es idiomático:

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

(O simplemente return Promise.reject(400);, pero de nuevo, entonces no hay información de contexto).

(En su caso, como está usando TypeScripty fooel valor de recuperación es Promise<A>, usaría return Promise.reject<A>(400 /*or error*/);)

En una situación async/ await, ese último es probablemente un poco de coincidencia semántica, pero funciona.

Si arroja un Error, eso juega bien con cualquier cosa que consuma el fooresultado de su awaitsintaxis:

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`.
}
TJ Crowder
fuente
12
Y dado que async / await se trata de recuperar el flujo asincrónico para sincronizar la sintaxis, throwes mejor que Promise.reject()IMO. Si throw 400es una pregunta diferente. En el OP está rechazando 400, y podemos argumentar que debería rechazar un Erroren su lugar.
unional
2
Sí, sin embargo, si su cadena de código realmente está usando async / wait, entonces ..... será difícil de escribir aquí, déjeme una demostración como respuesta
2017
1
¿Hay alguna razón por la que desee lanzar un nuevo error en lugar del error que se le dio en el bloque catch?
Adrian M
1
@sebastian - No sé a qué te refieres allí. En asyncfunciones, no hay resolveo rejectfunción. Hay returny throw, cuáles son las formas idiomáticas de resolver y rechazar la asyncpromesa de la función.
TJ Crowder
1
@ Jan-PhilipGehrcke - Usted puede , pero nunca lo hago. Está creando una instancia, lo newhace explícito. También tenga en cuenta que no puede dejarlo fuera si tiene una Errorsubclase ( class MyError extends Error), así que ...
TJ Crowder
146

Probablemente también debería mencionarse que simplemente puede encadenar una catch()función después de la llamada de su operación asincrónica porque, bajo el capó, aún se devuelve una promesa.

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

De esta forma, puede evitar la try/catchsintaxis si no le gusta.

David
fuente
1
Entonces, si quiero rechazar mi asyncfunción, lanzo una excepción y luego la atrapo muy bien, .catch()como si volviera Promise.rejecto llamara reject. ¡Me gusta!
icl7126
77
No entiendo por qué esta debería ser la respuesta aceptada. La respuesta aceptada no solo es más limpia, sino que también maneja todas las awaitfallas posibles en una rutina. A menos que se necesiten casos muy específicos para cada uno await, no veo por qué querría atraparlos así. Solo yo humilde opinión.
edgaralienfoe
1
@jablesauce para mi caso de uso, no solo necesitaba detectar cada awaitfalla por separado, sino que también necesitaba trabajar con un marco basado en Promise que rechazara las promesas por error.
Reuven Karasik
No me funcionó. No parece estar en el bloque de captura si la URL falla. [respuesta] = aguardar oauthGet ( ${host}/user/permissions/repositories_wrong_url/, accessToken, accessTokenSecret) .catch (err => {logger.error ('No se pueden recuperar los permisos del repositorio', err); devolución de llamada (err);})
sn.anurag
1
no necesita awaitpalabra clave aquí.
Ashish Rawat
12

Puede crear una función de contenedor que promete y devuelve una matriz con datos si no hay error y el error si hubo un error.

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

Úselo así en ES7 y en una función asíncrona :

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
}
Andy
fuente
1
Parece un intento de tener la encantadora sintaxis Go pero sin mucha elegancia. Encuentro que el código que lo usa está ofuscado lo suficiente como para absorber el valor de la solución.
Kim
8

Una mejor manera de escribir la función asíncrona sería devolviendo una Promesa pendiente desde el principio y luego manejando tanto los rechazos como las resoluciones dentro de la devolución de llamada de la promesa, en lugar de escupir una promesa rechazada por error. Ejemplo:

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

Luego, solo encadena métodos en la promesa devuelta:

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

bar()

Fuente: este tutorial:

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

OzzyTheGiant
fuente
55
La pregunta formulada específicamente sobre el uso de async / await. No uso de promesas
Mak
Esta respuesta no estaba destinada a ser la respuesta correcta definitiva. Esta fue una respuesta de apoyo a las otras respuestas dadas anteriormente. Lo habría dejado como un comentario, pero dado que tengo código, el campo de respuesta es un lugar mejor.
OzzyTheGiant
Gracias por aclararlo. Mostrar cómo hacer una función asíncrona es definitivamente útil. La actualización del segundo bloque de código para usar en espera será mucho más relevante y útil. Saludos
Mak
He editado tu respuesta para que se actualice. Avíseme si me perdí algo
Mak
4

Tengo una sugerencia para manejar adecuadamente los rechazos en un enfoque novedoso, sin tener múltiples bloques 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;
}

Donde se debe importar la función to.ts desde:

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

Los créditos van a Dima Grossman en el siguiente enlace .

Pedro Lourenço
fuente
1
Utilizo esta construcción casi exclusivamente (mucho más limpia) y hay un módulo 'a' que ha existido por un tiempo npmjs.com/package/await-to-js . No necesito la declaración por separado, solo ponga let delante de la tarea deconstruida. También puede hacerlo solo let [err]=si solo busca errores.
DKebler
3

Esta no es una respuesta sobre la de @TJ Crowder. Solo un comentario que responde al comentario "Y, de hecho, si la excepción se convertirá en un rechazo, no estoy seguro de si realmente me molesta si se trata de un error. Mis razones para arrojar solo el error probablemente no se apliquen. "

si su código está usando async/ await, entonces es una buena práctica rechazar con un en Errorlugar de 400:

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

Sé que esta es una vieja pregunta, pero acabo de tropezar con el hilo y parece haber una confusión entre errores y rechazo que entra en conflicto (al menos en muchos casos) con los consejos que se repiten a menudo para no usar el manejo de excepciones para lidiar con casos anticipados. Para ilustrar: si un método asincrónico está intentando autenticar a un usuario y la autenticación falla, es un rechazo (uno de los dos casos previstos) y no un error (por ejemplo, si la API de autenticación no estaba disponible).

Para asegurarme de que no solo estaba dividiendo pelos, realicé una prueba de rendimiento de tres enfoques diferentes para eso, usando este código:

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`);

Algunas de las cosas que están allí están incluidas debido a mi incertidumbre con respecto al intérprete de Javascript (solo me gusta ir por una madriguera a la vez); por ejemplo, incluí la doSomethingfunción y asigné su retorno a dummyValuepara garantizar que los bloques condicionales no se optimizaran.

Mis resultados fueron:

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

Sé que hay muchos casos en los que no vale la pena buscar pequeñas optimizaciones, pero en sistemas a gran escala, estas cosas pueden hacer una gran diferencia acumulativa, y esa es una comparación bastante dura.

ASÍ… aunque creo que el enfoque de la respuesta aceptada es sólido en los casos en que espera tener que manejar errores impredecibles dentro de una función asincrónica, en los casos en que un rechazo simplemente significa "tendrá que seguir el Plan B (o C o D ...) "Creo que mi preferencia sería rechazar el uso de un objeto de respuesta personalizado.

RiqueW
fuente
2
Además, recuerde que no necesita preocuparse por el manejo de errores imprevistos dentro de una función asincrónica si la llamada a esa función se encuentra dentro de un bloque try / catch en el alcance adjunto ya que, a diferencia de Promises, las funciones asincrónicas burbujean sus errores lanzados encerrando el alcance, donde se manejan como errores locales a ese alcance. ¡Esa es una de las principales ventajas de async / wait!
RiqueW
Los microbenchmarks son el diablo. Mire más de cerca los números. Debe estar haciendo algo 1000x para notar una diferencia de 1 ms aquí. Sí, agregar throw / catch desoptimizará la función. Pero a) si está esperando algo asíncrono, es probable que tome varios órdenes de magnitud que tarden más de 0,0005 ms en pasar en segundo plano. b) necesitas hacerlo 1000x para hacer una diferencia de 1 ms aquí.
Jamie Pate