Como rejeitar na sintaxe assíncrona / aguardar?


282

Como posso rejeitar uma promessa retornada por uma função assíncrona / aguardada?

por exemplo, originalmente

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

Traduzir para assíncrono / aguardar

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

Então, como eu poderia rejeitar adequadamente essa promessa neste caso?


20
Evite o Promiseconstrutor antipadrão ! Mesmo o primeiro trecho deve ter sido escritofoo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
— Bergi

10
Eu acho que seria útil traduzir o código desta pergunta em vanilla JS, pois a pergunta não tem nada a ver com o TypeScript. Se o fizesse, essa edição provavelmente seria aceita?
— Jacob Ford

Respostas:


326

Sua melhor aposta é a de throwuma Errorembalagem o valor, o que resulta em uma promessa rejeitado com um Errorembrulho o valor:

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

Você também pode apenas throwo valor, mas não há informações de rastreamento de pilha:

} catch (error) {
    throw 400;
}

Como alternativa, retorne uma promessa rejeitada com um Errorempacotamento do valor, mas não é idiomático:

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

(Ou apenas return Promise.reject(400);, mas novamente, não há informações de contexto.)

(No seu caso, como você está usando TypeScripte fooo valor de retrn Promise<A>, você usaria return Promise.reject<A>(400 /*or error*/);)

Em uma situação async/ await, esse último provavelmente é um erro de correspondência semântica, mas funciona.

Se você joga um Error, isso funciona bem com qualquer coisa que consome o fooresultado da sua com awaitsintaxe:

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 como async / waitit é sobre retornar o fluxo assíncrono à sintaxe de sincronização, throwé melhor que o Promise.reject()IMO. Se throw 400é uma pergunta diferente. No OP, ele está rejeitando 400, e podemos argumentar que ele deveria rejeitar um Error.
— unional

2
Sim, no entanto, se a sua cadeia de códigos estiver realmente usando async / waitit, você será ... muito difícil de digitar aqui, deixe-me demonstrar como resposta
— unional

1
existe alguma razão para você querer lançar um novo erro em oposição ao erro que lhe foi dado no bloco de captura?
— Adrian H

1
@ sebastian - Eu não sei o que você quer dizer lá. Nas asyncfunções, não existe resolveou rejectfunciona. Existem returne throw, quais são as formas idiomáticas de resolver e rejeitar a asyncpromessa da função.
— TJ Crowder

1
Jan-PhilipGehrcke - Você pode , mas eu nunca. Está criando uma instância, newtorna isso explícito. Além disso, note que você não pode deixá-lo fora se você tem uma Errorsubclasse ( class MyError extends Error), então ...
— TJ Crowder

146

Provavelmente, também deve ser mencionado que você pode simplesmente encadear uma catch()função após a chamada de sua operação assíncrona, porque, sob o capô, uma promessa ainda é retornada.

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

Dessa forma, você pode evitar a try/catchsintaxe se não gostar.


1
Portanto, se eu quiser rejeitar minha asyncfunção, ligo a exceção e a pego bem .catch()como se retornasse Promise.rejectou chamasse reject. Eu gosto disso!
— icl7126

7
Não entendo por que essa deve ser a resposta aceita. A resposta aceita não apenas é mais limpa, mas também lida com todas as awaitfalhas possíveis em uma rotina. A menos que casos muito específicos sejam necessários para cada um await, não vejo por que você gostaria de capturá-los assim. Apenas eu humilde opinião.
— Edgaralienfoe 4/09/18

1
O @jablesauce para o meu caso de uso, além de precisar detectar cada awaitfalha separadamente, também precisava trabalhar com uma estrutura baseada em Promessa que rejeitasse as promessas por erro.
— Reuven Karasik

Não funcionou para mim. Não parece estar entrando no bloco de captura se o URL falhar. [response] = aguarde oauthGet ( ${host}/user/permissions/repositories_wrong_url/, accessToken, accessTokenSecret) .catch (err => {logger.error ('Não foi possível obter permissões do repositório', err); callback (err);})
— sn.anurag

1
não precisa de awaitpalavra-chave aqui.
— Ashish Rawat

12

Você pode criar uma função de wrapper que aceite uma promessa e retorne uma matriz com dados se não houver erro e o erro se houver um erro.

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

Use-o assim no ES7 e em uma função assí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
}

1
Parece uma tentativa de ter a adorável sintaxe Go, mas sem muita elegância. Acho que o código que o utiliza é ofuscado apenas o suficiente para sugar o valor da solução.
— 21718 Kim

8

Uma maneira melhor de escrever a função assíncrona seria retornando uma promessa pendente desde o início e, em seguida, processando rejeições e resoluções no retorno de chamada da promessa, em vez de apenas citar uma promessa rejeitada por erro. Exemplo:

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

Então você apenas encadeia métodos na promessa retornada:

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

bar()

Fonte - este tutorial:

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


5
A pergunta feita especificamente sobre o uso de assíncrono / espera. Não usando promessas #
— 288 Mak

Esta resposta não pretendia ser a resposta correta definitiva. Esta foi uma resposta de suporte para as outras respostas dadas acima. Eu colocaria isso como um comentário, mas, como tenho código, o campo de resposta é um lugar melhor.
— OzzyTheGiant

Obrigado por esclarecer. Mostrar como criar uma função assíncrona é definitivamente útil. Atualizar o segundo bloco de código para usar wait será muito mais relevante e útil. Cheers
— Mak

Editei sua resposta para atualizá-la. Deixe-me saber se eu perdi alguma coisa
— Mak

4

Tenho uma sugestão para lidar adequadamente com rejeições em uma nova abordagem, sem ter vários blocos 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;
}

Para onde a função to.ts deve ser importada de:

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

Os créditos vão para Dima Grossman no link a seguir .


1
Eu uso essa construção quase que exclusivamente (muito mais limpo) e existe um módulo 'to' que existe há algum tempo npmjs.com/package/await-to-js . Não precisa da declaração separada, apenas coloque let na frente da tarefa desconstruída. Também pode fazer apenas let [err]=se apenas verificar erros.
— DKebler 8/01

3

Esta não é uma resposta à pergunta de @TJ Crowder. Apenas um comentário respondendo ao comentário "E, na verdade, se a exceção for convertida em uma rejeição, não tenho certeza se estou realmente incomodado se for um Erro. ​​Minhas razões para lançar apenas o Erro provavelmente não se aplicam. "

se o seu código estiver usando async/ await, ainda é uma boa prática rejeitar com um em Errorvez de 400:

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

3

Sei que essa é uma pergunta antiga, mas acabei de me deparar com o tópico e parece haver aqui uma confusão entre erros e rejeição que ocorre (em muitos casos, pelo menos) nos conselhos frequentemente repetidos de não usar o tratamento de exceções para lidar com casos previstos. Para ilustrar: se um método assíncrono está tentando autenticar um usuário e a autenticação falha, isso é uma rejeição (um dos dois casos previstos) e não um erro (por exemplo, se a API de autenticação não estiver disponível).

Para ter certeza de que não estava apenas dividindo os cabelos, executei um teste de desempenho com três abordagens diferentes, 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`);

Algumas das coisas que estão lá dentro estão incluídas por causa da minha incerteza em relação ao interpretador Javascript (eu só gosto de descer uma toca de coelho por vez); por exemplo, incluí a doSomethingfunção e designei seu retorno dummyValuepara garantir que os blocos condicionais não fossem otimizados.

Meus resultados foram:

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

Sei que existem muitos casos em que não vale a pena procurar pequenas otimizações, mas em sistemas de larga escala essas coisas podem fazer uma grande diferença cumulativa, e essa é uma comparação bastante clara.

ASSIM ... enquanto eu acho que a abordagem da resposta aceita é sólida nos casos em que você espera lidar com erros imprevisíveis em uma função assíncrona, nos casos em que uma rejeição significa simplesmente "você terá que seguir o plano B (ou C, ou D ...) "Acho que minha preferência seria rejeitar o uso de um objeto de resposta personalizado.


2
Além disso, lembre-se de que você não precisa se estressar ao lidar com erros imprevistos em uma função assíncrona se a chamada para essa função estiver dentro de um bloco try / catch no escopo envolvente, pois - ao contrário das funções promissoras - async enviam seus erros lançados ao abrangendo o escopo, onde eles são tratados como erros locais nesse escopo. Essa é uma das principais vantagens de assíncrono / espera!
— RiqueW

Microbenchmarks são o diabo. Olhe mais de perto os números. Você precisa fazer algo 1000x para notar uma diferença de 1ms aqui. Sim, adicionar throw / catch desativará a função. Mas a) se você estiver esperando por algo assíncrono, é provável que várias ordens de magnitude demorem mais de 0,0005 Ms para ocorrer em segundo plano. b) você precisa fazer isso 1000x para fazer uma diferença de 1ms aqui.
— Jamie Pate
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.