Preciso retornar após a resolução / rejeição antecipada?


262

Suponha que eu tenha o seguinte código.

function divide(numerator, denominator) {
 return new Promise((resolve, reject) => {

  if(denominator === 0){
   reject("Cannot divide by 0");
   return; //superfluous?
  }

  resolve(numerator / denominator);

 });
}

Se meu objetivo é rejectsair mais cedo, devo adquirir o hábito de também returnimediatamente depois?


Respostas:


371

O returnobjetivo é encerrar a execução da função após a rejeição e impedir a execução do código após ela.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {

    if (denominator === 0) {
      reject("Cannot divide by 0");
      return; // The function execution ends here 
    }

    resolve(numerator / denominator);
  });
}

Nesse caso, impede a resolve(numerator / denominator);execução, o que não é estritamente necessário. No entanto, ainda é preferível encerrar a execução para evitar uma possível interceptação no futuro. Além disso, é uma boa prática evitar a execução desnecessária de código.

fundo

Uma promessa pode estar em um dos três estados:

  1. pendente - estado inicial. De pendente, podemos passar para um dos outros estados
  2. cumprida - operação bem sucedida
  3. rejeitado - operação com falha

Quando uma promessa é cumprida ou rejeitada, ela permanece neste estado indefinidamente (liquidada). Portanto, rejeitar uma promessa cumprida ou cumprir uma promessa rejeitada não terá efeito.

Este trecho de exemplo mostra que, embora a promessa tenha sido cumprida após ser rejeitada, ela permaneceu rejeitada.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }

    resolve(numerator / denominator);
  });
}

divide(5,0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Então, por que precisamos retornar?

Embora não possamos alterar um estado de promessa estabelecida, rejeitar ou resolver não interromperá a execução do restante da função. A função pode conter código que criará resultados confusos. Por exemplo:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }
    
    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Mesmo que a função não contenha esse código no momento, isso cria uma possível armadilha futura. Um refatorador futuro pode ignorar o fato de que o código ainda é executado após a promessa ser rejeitada e será difícil depurar.

Parando a execução após resolver / rejeitar:

Esse é o material padrão do fluxo de controle JS.

  • Retornar após o resolve/ reject:

  • Retorno com resolve/ reject- como o valor de retorno do retorno de chamada é ignorado, podemos salvar uma linha retornando a instrução rejeitar / resolver:

  • Usando um bloco if / else:

Prefiro usar uma das returnopções, pois o código é mais plano.


28
Vale a pena notar que o código não se comportará de maneira diferente se returnestiver lá ou não, porque uma vez que um estado de promessa foi definido, ele não poderá ser alterado, portanto, a chamada resolve()após a chamada reject()não fará nada, exceto usar alguns ciclos extras da CPU. Eu próprio usaria o returnjust do ponto de vista de limpeza e eficiência do código, mas não é necessário neste exemplo específico.
— jfriend00

1
Tente usar em Promise.try(() => { })vez da nova promessa e evite usar chamadas de resolver / rejeitar. Em vez disso, você pode escrever que return denominator === 0 ? throw 'Cannot divide by zero' : numerator / denominator; eu uso Promise.trycomo meio de iniciar uma promessa e eliminar promessas envolvidas em blocos try / catch que são problemáticos.
— kingdango 27/09/16

2
É bom saber e eu gosto do padrão. No entanto, neste momento Promise.try é um estágio 0 sugestão, para que você só pode usá-lo com um calço ou usando uma biblioteca de promessa, como bluebird ou Q.
— Ori Drori

6
@ jfriend00 Obviamente, neste exemplo simples, o código não se comportará de maneira diferente. Mas e se você tivesse um código depois do rejectque faz algo caro, como conectar-se a bancos de dados ou pontos finais de API? Tudo isso seria desnecessário e custaria dinheiro e recursos, especialmente por exemplo, se você estiver se conectando a algo como um banco de dados da AWS ou um ponto de extremidade do API Gateway. Nesse caso, você definitivamente usaria um retorno para evitar que códigos desnecessários fossem executados.
— Jake Wilson

3
@JakeWilson - Claro, isso é apenas o fluxo normal de código em Javascript e não tem nada a ver com promessas. Se você terminar de processar a função e não desejar mais executar nenhum código no caminho atual, insira a return.
— jfriend00

37

Um idioma comum, que pode ou não ser sua xícara de chá, é combinar o returncom o reject, para rejeitar simultaneamente a promessa e sair da função, para que o restante da função, incluindo o, resolvenão seja executado. Se você gosta desse estilo, ele pode tornar seu código um pouco mais compacto.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) return reject("Cannot divide by 0");
                           ^^^^^^^^^^^^^^
    resolve(numerator / denominator);
  });
}

Isso funciona bem porque o construtor Promise não faz nada com nenhum valor de retorno e, em qualquer caso, resolvee rejectnão retorna nada.

O mesmo idioma pode ser usado com o estilo de retorno de chamada mostrado em outra resposta:

function divide(nom, denom, cb){
  if(denom === 0) return cb(Error("Cannot divide by zero"));
                  ^^^^^^^^^
  cb(null, nom / denom);
} 

Novamente, isso funciona bem porque a pessoa que divideestá chamando não espera que retorne nada e não faz nada com o valor de retorno.


6
Eu não gosto disso. Isso dá a noção de que você está retornando algo que de fato não está. Você está chamando a função rejeitar e, em seguida, usa return para finalizar a execução da função. Mantenha-os em linhas separadas, o que você está fazendo apenas confundirá as pessoas. A legibilidade do código é fundamental.
— K - A toxicidade no SO está crescendo.

7
@KarlMorrison, de fato, você está retornando "algo", uma promessa rejeitada. Eu acho que a "noção" de que você está falando é muito pessoal. Não há nada de errado em retornar um rejectstatus
— Frondor

5
@ Frondor Acho que você não entendeu o que escrevi. Claro que você e eu entendemos isso, nada acontece ao retornar uma rejeição na mesma linha. Mas e os desenvolvedores que não estão tão acostumados ao JavaScript que entram em um projeto? Esse tipo de programação diminui a legibilidade para essas pessoas. Hoje, o ecossistema JavaScript já é uma bagunça o suficiente e as pessoas que espalham esse tipo de prática só pioram. Isso é uma má prática.
— K - A toxicidade no SO está crescendo.

1
@KarlMorrison Opiniões pessoais! = Más práticas. Provavelmente, ajudaria um novo desenvolvedor Javascript a entender o que está acontecendo com o retorno.
— precisa saber é o seguinte

1
@TobyCaulk Se as pessoas precisam aprender com o retorno, não devem brincar com o Promises, devem aprender a programação básica.
— K - A toxicidade no SO está crescendo.

10

Tecnicamente , não é necessário aqui 1 - porque uma Promessa pode ser resolvida ou rejeitada, exclusivamente e apenas uma vez. O primeiro resultado da promessa vence e todos os resultados subsequentes são ignorados. Isso é diferente dos retornos de chamada no estilo do nó.

Dito isto, é uma boa prática limpa garantir que exatamente um seja chamado, quando prático, e, de fato, neste caso, uma vez que não há mais processamento assíncrono / adiado. A decisão de "retornar cedo" não é diferente de terminar qualquer função quando seu trabalho estiver concluído - em vez de continuar o processamento não relacionado ou desnecessário.

Retornar no momento apropriado (ou usar condicionais para evitar a execução do "outro caso") reduz a chance de executar código acidentalmente em um estado inválido ou de executar efeitos colaterais indesejados; e, como tal, torna o código menos propenso a 'quebrar inesperadamente'.


1 Essa resposta tecnicamente também depende do fato de que , neste caso, o código após o "retorno", caso seja omitido, não resultará em um efeito colateral. Felizmente , o JavaScript será dividido por zero e retornará + Infinity / -Infinity ou NaN.


Nota de rodapé agradável !!
— HankCa

9

Se você não "retornar" após uma resolução / rejeição, coisas ruins (como um redirecionamento de página) poderão acontecer depois que você tiver a intenção de parar. Fonte: Corri para isso.


6
+1 para o exemplo. Eu tive um problema em que meu programa fazia mais de 100 consultas inválidas ao banco de dados e não conseguia descobrir o motivo. Acontece que eu não "retornei" após uma rejeição. É um pequeno erro, mas eu aprendi minha lição.
— AdamInTheOculus 23/03

8

A resposta de Ori já explica que não é necessário, returnmas é uma boa prática. Observe que o construtor de promessas é seguro, portanto ignorará as exceções lançadas passadas posteriormente no caminho. Essencialmente, você tem efeitos colaterais que não podem ser observados facilmente.

Observe que o returninício precoce também é muito comum em retornos de chamada:

function divide(nom, denom, cb){
     if(denom === 0){
         cb(Error("Cannot divide by zero");
         return; // unlike with promises, missing the return here is a mistake
     }
     cb(null, nom / denom); // this will divide by zero. Since it's a callback.
} 

Portanto, embora seja uma boa prática em promessas, é necessário com retornos de chamada. Algumas notas sobre o seu código:

  • Seu caso de uso é hipotético, não use promessas com ações síncronas.
  • O construtor da promessa ignora os valores retornados. Algumas bibliotecas avisam se você retornar um valor não indefinido para avisá-lo contra o erro de retornar para lá. A maioria não é tão inteligente.
  • O construtor da promessa é seguro, converterá exceções em rejeições, mas como outros já apontaram - uma promessa é resolvida uma vez.

4

Em muitos casos, é possível validar parâmetros separadamente e retornar imediatamente uma promessa rejeitada com Promise.reject (reason) .

function divide2(numerator, denominator) {
  if (denominator === 0) {
    return Promise.reject("Cannot divide by 0");
  }
  
  return new Promise((resolve, reject) => {
    resolve(numerator / denominator);
  });
}


divide2(4, 0).then((result) => console.log(result), (error) => console.log(error));

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.