¿Por qué mi función asincrónica devuelve Promise {<pending>} en lugar de un valor?

129

Mi código:

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

Y cuando intento ejecutar algo como esto:

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

Me estoy poniendo:

Promise { <pending> }

¿Pero por qué?

Mi objetivo principal es obtener un token del google.login(data.username, data.password)que devuelve una promesa, en una variable. Y solo entonces realizar algunas acciones.

Src
fuente
1
@ LoïcFaure-Lacroix, vea este artículo: medium.com/@bluepnume/…
Src
@ LoïcFaure-Lacroix mira la getFirstUserfunción
Src
¿Y qué hay de eso? Es una función que devuelve una promesa.
Loïc Faure-Lacroix
1
@ LoïcFaure-Lacroix, por lo que quiere decir que incluso en ese ejemplo debemos usar entonces para acceder a la promesa de datos que regresa en la función getFirstUser.
Src
En ese ejemplo sí, la única otra forma es usar la sintaxis de ES7 "await" que parece resolver detener la ejecución del contexto actual para esperar el resultado de la promesa. Si lees el artículo, lo verás. Pero dado que ES7 probablemente no sea compatible en ninguna parte todavía, sí. El "entonces" es más o menos eso.
Loïc Faure-Lacroix

Respuestas:

176

La promesa siempre se registrará como pendiente siempre que sus resultados aún no se hayan resuelto. Debe recurrir .thena la promesa para capturar los resultados independientemente del estado de la promesa (resuelto o aún pendiente):

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

¿Porqué es eso?

Las promesas son solo una dirección hacia adelante; Solo puede resolverlos una vez. El valor resuelto de a Promisese pasa a sus métodos .theno .catch.

Detalles

De acuerdo con la especificación Promises / A +:

El procedimiento de resolución de promesas es una operación abstracta que toma como entrada una promesa y un valor, que denotamos como [[Resolver]] (promesa, x). Si x es un thenable, intenta hacer que la promesa adopte el estado de x, bajo el supuesto de que x se comporta al menos de alguna manera como una promesa. De lo contrario, cumple la promesa con el valor x.

Este tratamiento de las posibilidades permite que las implementaciones de las promesas interoperen, siempre que expongan un método que cumpla con las promesas / A +. También permite que las implementaciones de Promises / A + "asimilen" las implementaciones no conformes con métodos razonables.

Esta especificación es un poco difícil de analizar, así que analicémosla. La regla es:

Si la función en el .thencontrolador devuelve un valor, entonces se Promiseresuelve con ese valor. Si el manejador devuelve otro Promise, entonces el original se Promiseresuelve con el valor resuelto del encadenado Promise. El siguiente .thencontrolador siempre contendrá el valor resuelto de la promesa encadenada devuelta en el anterior .then.

La forma en que realmente funciona se describe a continuación con más detalle:

1. La devolución de la .thenfunción será el valor resuelto de la promesa.

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. Si la .thenfunción devuelve un Promise, entonces el valor resuelto de esa promesa encadenada se pasa a lo siguiente .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
fuente
El primero no funciona. Uncaught SyntaxError: Unexpected token .. El segundo necesita una devolución paraPromise
zamil
@zamil tienes que invocar la función, como en el segundo ejemplo. no puedes .thenen una función no invocada. actualizó la respuesta
Bamieh
1
Estoy marcando esto como favorito para poder guardarlo para siempre. He trabajado MUY largo tiempo para encontrar reglas realmente claras y legibles sobre cómo construir promesas. Su cita en bloque de Promises / A + spec es un ejemplo perfecto de por qué ha sido un PITA para autoaprender promesas. También es la ÚNICA vez que he visto usar setTimeout en el que no confundió la lección en sí. Y excelente referencia, gracias.
monsto
21

Sé que esta pregunta se hizo hace 2 años, pero me encontré con el mismo problema y la respuesta al problema es que desde ES6, simplemente puede que awaitlas funciones devuelvan 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
fuente
3
No necesita el .then(token => return token), eso es solo un paso a través innecesario. Simplemente devuelva la llamada de inicio de sesión de Google.
Soviut
Esta respuesta no está relacionada con la pregunta. El problema del póster original no tiene nada que ver con ES6 'async / await. Las promesas existían antes de que se introdujera este nuevo azúcar sintáctico en ECMAScript 2017 y usaban Promesas "bajo el capó". Consulte MDN en async / await .
try-catch-finalmente
Para ES8 / Nodejs, se generan errores si usa awaitfuera de una función asíncrona. Quizás el mejor ejemplo aquí sería hacer la AuthUserfunción async, que luego termina conreturn await google.login(...);
Jon L.
4

El thenmétodo devuelve una promesa pendiente que puede resolverse de forma asincrónica mediante el valor de retorno de un controlador de resultados registrado en la llamada a then, o rechazada arrojando un error dentro del controlador llamado.

Por lo tanto, las llamadas AuthUserno registrarán repentinamente al usuario de forma síncrona, sino que devolverán una promesa cuyos controladores registrados en ese momento serán llamados después de que el inicio de sesión sea exitoso (o no). Sugeriría activar todo el procesamiento de inicio de sesión mediante una thencláusula de la promesa de inicio de sesión. EG usando funciones nombradas para resaltar la secuencia de flujo:

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
fuente
1

Consulte la sección MDN sobre promesas. En particular, observe el tipo de devolución de then ().

Para iniciar sesión, el agente de usuario debe enviar una solicitud al servidor y esperar a recibir una respuesta. Dado que hacer que su aplicación detenga por completo la ejecución durante un viaje de ida y vuelta de solicitud generalmente genera una mala experiencia de usuario, prácticamente todas las funciones de JS que lo inician (o realizan cualquier otra forma de interacción con el servidor) usarán una Promesa, o algo muy parecido , para entregar resultados de forma asincrónica.

Ahora, observe también que las returndeclaraciones siempre se evalúan en el contexto de la función en la que aparecen. Entonces, cuando escribió:

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

la declaración return token;significaba que la función anónima a la que se pasa then()debería devolver el token, no que la AuthUserfunción debería. Lo que AuthUserregresa es el resultado de llamar google.login(username, password).then(callback);, que resulta ser una Promesa.

En última instancia, su devolución de llamada token => { return token; }no hace nada; en cambio, su entrada a then()debe ser una función que realmente maneje el token de alguna manera.

Jesse Amano
fuente
@Src Escribí mi respuesta antes de que el autor de la pregunta aclarara que estaban buscando una forma de devolver un valor de forma sincrónica , y sin hacer suposiciones sobre su entorno de desarrollo o versión de idioma más allá de lo que podría inferirse mediante el fragmento de código, es decir, es seguro asumir ES6, pero no necesariamente ES7.
Jesse Amano
@AhmadBamieh Está bien, servirá. Supongo que el problema es que he entendido mal cómo returnse trata con la nueva (ish) sintaxis de cierre, en cuyo caso, bueno, lo desapruebo enérgicamente, pero el error sigue siendo mío y me disculpo por ello.
Jesse Amano
2
@AhmadBamieh Er, en realidad sabía esa parte, por lo que afirmé que token => { return token; } no hace nada en lugar de afirmar que es contraproducente. Puede decir google.login(username, password).then(token=>{return token;}).then(token=>{return token;})y así sucesivamente para siempre, pero solo logrará devolver un Promiseque se resuelva con un token, lo mismo que si lo dejara como google.login(username, password);. No estoy seguro de por qué siente que esto está "muy mal".
Jesse Amano
1
@AhmadBamieh: ¿puedes ser más específico sobre lo que está mal en este texto? No veo nada, solo explica por qué return tokenno funciona como probablemente esperaba el OP.
Bergi
3
@AhmadBamieh: de hecho, hay un malentendido. Los tres sabemos bien cómo funcionan las promesas, la declaración es que promise.then(result => { return result; })es exactamente equivalente a promise, por lo tanto, la llamada al método no hace nada y debe eliminarse para simplificar el código y mejorar la legibilidad, una declaración que es completamente cierta.
Bergi
1

Tu promesa está pendiente, complétala antes de

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

después de su código restante. Todo lo que hace este código es que .then()completa su promesa y captura el resultado final en la variable de resultado y el resultado de impresión en la consola. Tenga en cuenta que no puede almacenar el resultado en una variable global. Espero que esa explicación te ayude.

Naveen Nirban Yadav
fuente