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?

Fénix
fonte
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`.
}
TJ Crowder
fonte
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.

David
fonte
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
}
Andy
fonte
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

OzzyTheGiant
fonte
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 .

Pedro Lourenço
fonte
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`
}
unional
fonte
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.

RiqueW
fonte
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