Por que minha função assíncrona está retornando Promise {<pending>} em vez de um valor?

128

Meu código:

let AuthUser = data => {
  return google.login(data.username, data.password).then(token => { return token } )
}

E quando tento executar algo assim:

let userToken = AuthUser(data)
console.log(userToken)

Estou entendendo:

Promise { <pending> }

Mas por que?

Meu principal objetivo é obter um token do google.login(data.username, data.password)qual retorna uma promessa, em uma variável. E só então realizar algumas ações.

Src
fonte
1
@ LoïcFaure-Lacroix, veja este artigo: medium.com/@bluepnume/…
Src
@ LoïcFaure-Lacroix, veja a getFirstUserfunção
Src
E quanto a isso? É uma função que retorna uma promessa.
Loïc Faure-Lacroix
1
@ LoïcFaure-Lacroix, então você quer dizer que mesmo nesse exemplo precisamos usar então para acessar a promessa de dados retornada na função getFirstUser?
Src
Nesse exemplo sim, a única outra forma é usar a sintaxe ES7 "await" que parece resolver parar a execução do contexto atual para aguardar o resultado da promessa. Se você ler o artigo, você o verá. Mas como o ES7 provavelmente não tem suporte em lugar nenhum, sim. O "então" é basicamente isso.
Loïc Faure-Lacroix

Respostas:

175

A promessa sempre será registrada pendente, desde que seus resultados ainda não sejam resolvidos. Você deve invocar .thena promessa para capturar os resultados, independentemente do estado da promessa (resolvida ou ainda pendente):

let AuthUser = function(data) {
  return google.login(data.username, data.password).then(token => { return token } )
}

let userToken = AuthUser(data)
console.log(userToken) // Promise { <pending> }

userToken.then(function(result) {
   console.log(result) // "Some User token"
})

Por que é que?

As promessas são direcionadas apenas para a frente; Você só pode resolvê-los uma vez. O valor resolvido de a Promiseé passado para seus métodos .thenou .catch.

Detalhes

De acordo com a especificação Promises / A +:

O procedimento de resolução de promessa é uma operação abstrata tomando como entrada uma promessa e um valor, que denotamos como [[Resolver]] (promessa, x). Se x for um então, ele tenta fazer a promessa adotar o estado de x, sob a suposição de que x se comporta pelo menos um pouco como uma promessa. Caso contrário, cumpre a promessa com o valor x.

Esse tratamento dos objetos permite que implementações promissoras interoperem, contanto que exponham um método then compatível com Promises / A +. Também permite que as implementações do Promises / A + “assimilem” implementações não conformes com métodos then razoáveis.

Esta especificação é um pouco difícil de analisar, então vamos decompô-la. A regra é:

Se a função no .thenmanipulador retornar um valor, o será Promiseresolvido com esse valor. Se o manipulador retornar outro Promise, o original será Promiseresolvido com o valor resolvido do encadeado Promise. O próximo .thenmanipulador sempre conterá o valor resolvido da promessa encadeada retornada na anterior .then.

A maneira como realmente funciona é descrita abaixo com mais detalhes:

1. O retorno da .thenfunção será o valor resolvido da promessa.

function initPromise() {
  return new Promise(function(res, rej) {
    res("initResolve");
  })
}

initPromise()
  .then(function(result) {
    console.log(result); // "initResolve"
    return "normalReturn";
  })
  .then(function(result) {
    console.log(result); // "normalReturn"
  });

2. Se a .thenfunção retornar a Promise, o valor resolvido dessa promessa encadeada será passado para o seguinte .then.

function initPromise() {
  return new Promise(function(res, rej) {
    res("initResolve");
  })
}

initPromise()
  .then(function(result) {
    console.log(result); // "initResolve"
    return new Promise(function(resolve, reject) {
       setTimeout(function() {
          resolve("secondPromise");
       }, 1000)
    })
  })
  .then(function(result) {
    console.log(result); // "secondPromise"
  });
Bamieh
fonte
Seu primeiro não está funcionando. Uncaught SyntaxError: Unexpected token .. O segundo precisa ser devolvido paraPromise
zamil
@zamil você deve chamar a função, como no segundo exemplo. você não pode fazer .thenuma função não invocada. atualizou a resposta
Bamieh
1
Estou marcando isso para que possa mantê-lo para sempre. Tenho trabalhado MUITO tempo para encontrar regras realmente claras e legíveis de como realmente construir promessas. Seu blockquote de Promises / A + spec é um exemplo perfeito de porque tem sido um PITA auto-ensinar promessas. É também a ÚNICA vez que vi setTimeout usado sem confundir a lição em si. E excelente referência, obrigado.
Monsto
21

Sei que essa pergunta foi feita há 2 anos, mas me deparei com o mesmo problema e a resposta para o problema é desde ES6, que você pode simplesmente awaitas funções retornar valor, como:

let AuthUser = function(data) {
  return google.login(data.username, data.password).then(token => { return token } )
}

let userToken = await AuthUser(data)
console.log(userToken) // your data
Marius Seack
fonte
3
Você não precisa do .then(token => return token), isso é apenas uma passagem desnecessária. Basta retornar a chamada de login do google.
Soviut
Esta resposta não tem relação com a pergunta. O problema do autor original não tem nada a ver com ES6 'async / await. As promessas existiam antes de esse novo açúcar sintático ser introduzido no ECMAScript 2017 e eles usavam o Promises "por baixo do capô". Veja MDN em async / await .
tente pegar finalmente
Para ES8 / Nodejs, erros são lançados se você usar awaitfora de uma função assíncrona. Talvez o melhor exemplo aqui seja fazer a AuthUserfunção async, que termina comreturn await google.login(...);
Jon L.
4

O thenmétodo retorna uma promessa pendente que pode ser resolvida de forma assíncrona pelo valor de retorno de um manipulador de resultados registrado na chamada para then, ou rejeitado lançando um erro dentro do manipulador chamado.

Portanto, a chamada AuthUsernão logará repentinamente o usuário de forma síncrona, mas retornará uma promessa cujos manipuladores registrados serão chamados depois que o login for bem-sucedido (ou falhar). Eu sugeriria acionar todo o processamento de login por uma thencláusula da promessa de login. EG usando funções nomeadas para destacar a sequência de fluxo:

let AuthUser = data => {   // just the login promise
  return google.login(data.username, data.password);
};

AuthUser(data).then( processLogin).catch(loginFail);

function processLogin( token) {
      // do logged in stuff:
      // enable, initiate, or do things after login
}
function loginFail( err) {
      console.log("login failed: " + err);
}
traktor53
fonte
1

Consulte a seção MDN sobre Promessas. Em particular, observe o tipo de retorno de then ().

Para efetuar login, o agente do usuário deve enviar uma solicitação ao servidor e aguardar o recebimento de uma resposta. Já que fazer seu aplicativo parar totalmente a execução durante uma viagem de ida e volta de solicitação geralmente causa uma experiência ruim para o usuário, praticamente todas as funções JS que conectam você (ou realizam qualquer outra forma de interação do servidor) usarão uma promessa ou algo muito parecido , para entregar resultados de forma assíncrona.

Agora, observe também que as returninstruções são sempre avaliadas no contexto da função em que aparecem. Então, quando você escreveu:

let AuthUser = data => {
  return google
    .login(data.username, data.password)
    .then( token => {
      return token;
    });
};

a instrução return token;significava que a função anônima sendo passada then()deveria retornar o token, não que a AuthUserfunção deveria. O que AuthUserretorna é o resultado do chamado google.login(username, password).then(callback);, que passa a ser uma promessa.

Em última análise, seu retorno de chamada token => { return token; }não faz nada; em vez disso, sua entrada para then()precisa ser uma função que realmente lida com o token de alguma forma.

Jesse Amano
fonte
@Src Eu escrevi minha resposta antes que o autor da pergunta esclarecesse que eles estavam procurando uma maneira de retornar um valor de forma síncrona e sem fazer suposições sobre seu ambiente de desenvolvimento ou versão de linguagem além do que poderia ser inferido pelo snippet de código - ou seja, é seguro para assumir ES6, mas não necessariamente ES7.
Jesse Amano
@AhmadBamieh Tudo bem, vou servir. Presumo que o problema é que não entendi como returna nova sintaxe de encerramento (ish) é tratada, nesse caso - bem, desaprovo veementemente isso, mas o erro ainda é meu e peço desculpas por ele.
Jesse Amano
2
@AhmadBamieh Er, na verdade eu conhecia essa parte, e é por isso que afirmei que token => { return token; } não faz nada ao invés de alegar que era contraproducente. Você pode dizer google.login(username, password).then(token=>{return token;}).then(token=>{return token;})e assim por diante para sempre, mas só conseguirá retornar um Promiseque resolva com um token - o mesmo como se tivesse deixado como google.login(username, password);. Não sei por que você acha que isso é "muito errado".
Jesse Amano
1
@AhmadBamieh: você pode ser mais específico sobre o que há de errado neste pedaço de texto? Não vejo nada, ele apenas explica por return tokenque não funciona como o OP provavelmente esperava.
Bergi
3
@AhmadBamieh: realmente existe um mal-entendido. Todos nós três sabemos bem como as promessas funcionam, a declaração é promise.then(result => { return result; })exatamente equivalente a promise, portanto, a chamada do método não faz nada e deve ser descartada para simplificar o código e melhorar a legibilidade - uma declaração que é completamente verdadeira.
Bergi
1

Sua promessa está pendente, conclua-a até

userToken.then(function(result){
console.log(result)
})

após o código restante. Tudo o que esse código faz é .then()completar sua promessa e capturar o resultado final na variável de resultado e imprimir o resultado no console. Lembre-se de que você não pode armazenar o resultado em uma variável global. Espero que essa explicação possa ajudá-lo.

Naveen Nirban Yadav
fonte