Stack Overflow en español Asked by MANZARBEITIA on December 17, 2020
lo primero gracias por molestarte en entrar en la pregunta.
Estoy creando un pequeño backend server para una app web, este backend crea un Api REST que se nutre de otras APIs y a su vez guarda los datos en una bbdd por si se caen las APIs externas.
La estructura de carpetas de mi proyecto que incumbe a mi duda es la siguiente:
**ESTRUCTURA CARPETAS**
└───routes
├───pre
└───pro
____index.js
En el Index creo un servidor Express cuyo codigo es el siguiente:
**INDEX JS**
require('dotenv').config();
const express = require('express');
const cors = require('cors');
//Crear servidor express
const app = express();
//Configurar cors
app.use(cors());
//Rutas
app.use('/pre/apis', require('./routes/pre/apis'));
//Puerto
app.listen(process.env.PORT, () => {
console.log(`Servidor corriendo en puerto ${ process.env.PORT }`)
});
Como podéis ver en una de las rutas llamo al archivo apis (en carpeta routes/pre) a través de un require el cual es el siguiente:
**ARCHIVO APIS JS --> Routes/pre/apis.js**
const axios = require('axios');
const { Router } = require('express');
const { getToken } = require('./getToken');
const router = Router();
const token = getToken;
console.log(token);
module.exports = router;
Y a su vez aquí llamo a un archivo que sirve para generar un token (getToken) de acceso a la respectiva API externa, este archivo tambien se encuentra en la misma carpeta (routes/pre)
**ARCHIVO GETTOKEN JS --> routes/pre/getToken.js**
const axios = require('axios');
const getToken = async() => {
let token;
const instance = axios.create({
baseURL: ''
});
await instance.post()
.then(r => {
token = r.data.access_token
return token;
}, e => {
return e;
});
}
module.exports = getToken;
Bien llegados a este punto es cuando viendo mi error comienzan mis dudas sobre como funcionan los requires.
Si ejecuto el archivo getToken directamente SI me genera un token y me lo imprime en consola, sin embargo si ejecuto indexjs automaticamente me responde con un "undefined" que proviene del console.log() que realizo en el archivo apis.
¿Sabéis cuál puede ser la solución?
Si has llegado hasta aquí, muchas gracias =)
PROBLEMA
El problema que tienes es el manejo de los procesos asíncronos en tu código. Nada de eso tiene que ver con la forma en que trabaja el método require()
.
Actualmente, en tu código del módulo getToken.js
, tienes una llamada a un proceso asíncrono:
await instance.post()
Y aunque la función getToken
la declaras con la sentencia async
, no estás tratando el resultado de dicha función como lo que es: una Promesa.
SOLUCIÓN
La solución pasa primero por entender el proceso asíncrono del método de instancia post()
de axios
, el cual devuelve una Promesa.
Una Promesa en esencia lo que su palabra significa: la promesa de obtener un resultado a futuro. Y es debido a esta naturaleza (y a la naturaleza del lenguaje javascript) que nunca podremos garantizar cuándo un resultado devuelto por una Promesa esté disponible para ser usado (en una secuencia lineal o síncrona) en nuestro programa.
Hemos de tomar en cuenta que toda función tipo async
devuelve una Promesa (realmente devuelve una instancia de una AsyncFunction
).
Por otro lado, al método then()
le estás pasando una segunda función, cosa que no es correcto, ya que then
sólo espera 1 función callback
. Lo que haces con la segunda función deberías pasarlo al método catch()
:
let token = await instancia.post()
.then(callback_1)
.catch(callback_2);
Para entender el funcionamiento de un método asíncrono, veamos el siguiente ejemplo:
const saludar = async someone => {
if(!someone) {
someone = '';
}
return `¡Hola ${someone}!`
}
const miSaludo = async () => {
saludo = await saludar('Mauricio').then(resultado => {
console.log(`Obtuve este resultado: ${resultado}`);
});
console.log(`miSaludo(): ${saludo}`);
}
const miSaludoReturn = async () => {
saludo = await saludar('MANZARBEITIA').then(resultado => {
return resultado;
});
console.log(`miSaludoReturn(): ${saludo}`);
}
const miSaludoSinThen = async () => {
saludo = await saludar();
console.log(`miSaludoSinThen: ${saludo}`);
}
miSaludo();
miSaludoReturn();
miSaludoSinThen();
.as-console-wrapper {
min-height: 100%;
top: 0;
}
En el código anterior se tienen 3 casos de llamadas a funciones asíncronas.
En el primer caso, se hace una llamada a la función saludar
usando la sentencia await
, y el resultado se asigna a la variable saludo
. Además, se utiliza el método then()
, sin embargo dentro del mismo no se devuelve el resultado, solo se muestra por consola. Podemos observar que el resultado asignado a la variable saludo
es undefined
.
En el segundo caso, se hace la misma rutina que en el primer caso, pero introducimos una sentencia return
dentro del método then
, por lo cual, se devuelve una Promesa. Ya que hemos usado una sentencia await
, el resultado de esta Promesa (la variable result
pasada como argumento al callback
en then
) se asigna a la variable saludo
.
En el tercer caso, no usamos el método then
. Simplemente se recibe el resultado de la función saludar
en la variable saludo
. De esta forma el código es incluso más legible, y sería la forma preferida de usar resultados de una Promesa directamente, a menos que queramos manipular el mismo antes de asignarlo a la variable donde será recibido.
Aclarados estos puntos, podemos ajustar tu función getToken
para que la misma utilice correctamente then
y catch
. Además, ajustaremos el código de tu aplicación para que puedas usar correctamente el valor devuelto por esta función.
// ARCHIVO GETTOKEN JS --> routes/pre/getToken.js
const axios = require('axios');
// función tipo async: devuelve una Promesa
const getToken = async() => {
const instance = axios.create({
baseURL: 'https://auth.es-pre.baikalplatform.com/token?client_id=baikal-api-read&client_secret=B6Dk9CBsfouihKIo8WNh&grant_type=client_credentials'
});
let token = await instance.post()
.then(r => {
return r.data.access_token;
})
.catch(e => {
// debemos decidir qué hacer con el posible error, lo usual es hacer un throw
throw e;
});
// ahora podemos devolver el token
return token;
}
// ahora podemos exportar nuestra función (recordar que es una Promesa)
module.exports = getToken;
Ahora que ya sabemos que nuestro módulo getToken
devuelve una Promesa, debemos usarlo como tal al momento de hacer el require
.
Por ejemplo:
// ARCHIVO APIS JS --> Routes/pre/apis.js**
const axios = require('axios');
const { Router } = require('express');
// getToken devuelve una Promesa, recordar eso.
const { getToken } = require('./getToken');
const router = Router();
// getToken es una Promesa, para usar su resultado debo usar then y catch
getToken
.then(token => {
console.log(token);
})
.catch(error => {
console.log(`Ocurrió un error al obtener el token: ${error.message}`);
});
module.exports = router;
De esta forma al momento de hacer el require
de nuestro módulo apis.js
se ejecutará la llamada al método getToken
y una vez obtenido el resultado se mostrará en pantalla. Si la llamada de axios
arrojara algún error, el mismo será capturado por el método catch()
de nuestro módulo getToken
y como en el mismo estamos simplemente lanzando dicho error, el mismo será capturado por nuestro método catch
en apis.js
donde nos mostrará el mensaje de error.
Para usar el valor del token
siempre debemos recordar que el mismo es el resultado de un proceso asíncrono (el método post
de axios
es asíncrono), por lo cual, la única forma de tener acceso a dicho resultado será dentro de una función callback
pasada como argumento al método then
o usando la sentencia await
(dentro de una función o entorno async
) para esperar directamente por dicho resultado.
Espero que esto te ayude a resolver el problema y aclare un poco más el uso de procesos asíncronos.
Correct answer by Mauricio Contreras on December 17, 2020
El problema como comentan es que no estas esperando; donde sea que lo estas requiriendo, al resolve de la funcion, pues cuando haces que una función sea asincrona la conviertes en una promesa automaticamente.
La solución al problema es simple, utilizar then
para obtener el retorno, pero esto lo convierte en un problema de callbacks (o no), asi que si estas utilizando express puedes siempre puesdes convertir en asincrona el callback de la request:
router.get('/apiKey', async (req, res) => await getToken)
La otra solución que es la mejor, es utilizar las ultimas versiones de node que admiten soporte para Top-level await como lo dice en este changelog de v8
Haciendolo tan sencillo como en Deno:
// Top-level scope
let apikey = await getToken
Anteriormente esto no hubiese funcionado y tendrias que haber hecho algo como
// Top-level scope
(async function() {
let apikey = await getToken;
}());
EDIT:
Igualmente me doy cuenta de que no estas retornando la función como deberias, para esto solo cambia un par de lineas:
const getToken = async () => {
let token;
const instance = axios.create({
baseURL: 'https://auth.es-pre.baikalplatform.com/token?client_id=baikal-api-read&client_secret=B6Dk9CBsfouihKIo8WNh&grant_type=client_credentials'
});
let res = await instance.post();
let { data } = res:
return data.access_token
}
Espero que te sea de ayuda, un saludo.
Answered by Dєηyη Crawford on December 17, 2020
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP