La función asíncrona no devuelve valor, pero console.log () sí lo hace: ¿cómo hacerlo?

Tengo una clase es6, con un método init() responsable de buscar datos, transformarlos y luego actualizar la propiedad de la clase this.data con los datos recién transformados. Hasta aquí todo bien. La clase en sí tiene otro método getPostById() para hacer lo que parece. Aquí está el código para la clase:

 class Posts { constructor(url) { this.ready = false this.data = {} this.url = url } async init() { try { let res = await fetch( this.url ) if (res.ok) { let data = await res.json() // Do bunch of transformation stuff here this.data = data this.ready = true return data } } catch (e) { console.log(e) } } getPostById(id){ return this.data.find( p => p.id === id ) } } 

Directo, excepto que tengo un mecanismo async/await en el método init() . Ahora, este código funcionará correctamente:

 let allPosts = new Posts('https://jsonplaceholder.typicode.com/posts') allPosts.init() .then( d => console.log(allPosts.getPostById(4)) ) // resulting Object correctly logged in console 

pero solo se imprime en la consola: ¿cómo podría usar allPosts.getPostById(4) como return de una función?

Me gusta:

 let myFunc = async () => { const postId = 4 await allPosts.init() // I need to wait for this to finish before returning // This is logging correct value console.log( 'logging: ' + JSON.stringify(allPosts.getPostById( postId ), null, 4) ) // How can I return the RESULT of allPosts.getPostById( postId ) ??? return allPosts.getPostById( postId ) } 

myFunc() devuelve una Promise pero no el valor final. He leído varias publicaciones relacionadas sobre el tema, pero todas dan ejemplos de registro, nunca regresan.

Aquí hay un violín que incluye dos formas de manejar init() : usando Promise y usando async/await . No importa lo que intente, no puedo getPostById(id) VALOR FINAL de getPostById(id) .

La pregunta de esta publicación es: ¿cómo puedo crear una función que DEVUELVA el VALOR de getPostById(id) ?

EDITAR:

Muchas buenas respuestas tratando de explicar qué son las Promesas en relación con el ciclo principal de ejecución. Después de muchos videos y otras buenas lecturas, esto es lo que entiendo ahora:

mi función init() regresa correctamente. Sin embargo, dentro del ciclo de evento principal: devuelve una Promesa , entonces es mi trabajo capturar el resultado de esta Promesa dentro de un bucle paralelo similar (no un nuevo hilo real). Para capturar el resultado del bucle paralelo hay dos formas:

  1. use .then( value => doSomethingWithMy(value) )

  2. use let value = await myAsyncFn() . Ahora aquí está el estúpido hipo:

await solo se puede usar dentro de una función async : p

devolviendo así una Promesa, utilizable con la await que debe ser incorporada en una función async , que será utilizable con la await etc.

Esto significa que realmente no podemos ESPERAR una Promesa: en su lugar deberíamos capturar el bucle paralelo indefinidamente: usando .then() o async/await .

Gracias por la ayuda !

En cuanto a tu comentario; Lo agregaré como respuesta.

El código que escribe en JavaScript se ejecuta en un hilo, lo que significa que si su código realmente puede esperar algo, bloqueará la ejecución de cualquiera de los otros códigos. El ciclo de evento de JavaScript se explica muy bien en este video y si le gusta leer en esta página .

Un buen ejemplo de código de locking en el navegador es la alert("cannot do anything until you click ok"); . La alerta bloquea todo, el usuario ni siquiera puede desplazarse o hacer clic en cualquier elemento de la página y tu código también bloquea su ejecución.

 Promise.resolve(22) .then(x=>alert("blocking")||"Hello World") .then( x=>console.log( "does not resolve untill you click ok on the alert:", x ) ); 

Ejecuta eso en una consola y verás lo que quiero decir con el locking.

Esto crea un problema cuando quieres hacer algo que lleva tiempo. En otros marcos, usaría un hilo o procesos, pero no existe tal cosa en JavaScript (técnicamente existe con el trabajador web y fork en el nodo, pero esa es otra historia y, por lo general, es mucho más complicado que usar asys api).

Entonces, cuando desee realizar una solicitud http, puede usar fetch pero fetch tarda un poco en terminar y su función no debe bloquearse (tiene que devolver algo lo más rápido posible). Esta es la razón por la cual fetch devuelve una promesa.

Tenga en cuenta que fetch es implementado por navegador / nodo y se ejecuta en otro hilo, solo el código que escribe se ejecuta en un hilo, por lo que comenzar muchas promesas que solo ejecutan código no acelerará nada más que llamar a api asíncronas nativas en forma paralela.

Antes de las promesas, el código asíncrono utilizaba las devoluciones de llamada o devolvía un objeto observable (como XmlHttpRequest), pero cubrimos las promesas, ya que puedes convertir el código más tradicional en una promesa de todos modos.

Una promesa es un objeto que tiene una función then (y un montón de cosas que es azúcar para entonces pero hace lo mismo), esta función toma 2 parámetros.

  1. Controlador de resolución: una función que será invocada por la promesa cuando la promesa se resuelva (no tenga errores y haya finalizado). La función se pasará un argumento con el valor de resolución (para las solicitudes http esta suele ser la respuesta).
  2. Rechazar controlador: una función que será invocada por la promesa cuando la promesa se rechace (tiene un error). A esta función se le pasará un argumento; este suele ser el error o el motivo del rechazo (puede ser una cadena, un número o cualquier otra cosa).

Conversión de callback a promesa.

Las api tradicionales (especialmente las api de nodejs) usan devoluciones de llamada:

 traditionalApi( arg ,function callback(err,value){ err ? handleFail(err) : processValue(value); } ); 

Esto hace que sea difícil para el progtwigdor detectar errores o manejar el valor de retorno de forma lineal (de arriba hacia abajo). Es aún más imposible intentar y hacer cosas paralelas o aceleradas en paralelo con el manejo de errores (imposible de leer).

Puede convertir aplicaciones tradicionales a promesas con una new Promise

 const apiAsPromise = arg => new Promise( (resolve,reject)=> traditionalApi( arg, (err,val) => (err) ? reject(err) : resolve(val) ) ) 

async aguarda

Esto es lo que se llama azúcar sintáctica para las promesas. Hace que las funciones consumidoras prometedoras se vean más tradicionales y más fáciles de leer. Es decir, si te gusta escribir código tradicional, yo diría que componer funciones pequeñas es mucho más fácil de leer. Por ejemplo, ¿puedes adivinar qué hace esto ?:

 const handleSearch = search => compose([ showLoading, makeSearchRequest, processRespose, hideLoading ])(search) .then( undefined,//don't care about the resolve compose([ showError, hideLoading ]) ); 

Anayway; suficiente despotricando. La parte importante es entender que async await realidad no inicia otro subproceso, las funciones async siempre devuelven una promesa y await que en realidad no se bloquee ni espere. Es azúcar de syntax para someFn().then(result=>...,error=>...) y se ve así:

 async someMethod = () => //syntax sugar for: //return someFn().then(result=>...,error=>...) try{ const result = await someFn(); ... }catch(error){ ... } } 

Los ejemplos siempre muestran try catch pero no es necesario hacer eso, por ejemplo:

 var alwaysReject = async () => { throw "Always returns rejected promise"; }; alwaysReject() .then( x=>console.log("never happens, doesn't resolve") ,err=>console.warn("got rejected:",err) ); 

Cualquier error lanzado o en await devolver una promesa rechazada provocará que la función asincrónica devuelva una promesa rechazada (a menos que intente atraparla). Muchas veces es deseable dejar que falle y que la persona que llama maneje los errores.

Los errores de captura podrían ser necesarios cuando desee que la promesa tenga éxito con un valor especial para las promesas rechazadas para que pueda manejarlo más adelante, pero la promesa no es técnicamente rechazada, por lo que siempre se resolverá.

Un ejemplo es Promise.all , esto toma una serie de promesas y devuelve una nueva promesa que se resuelve en una matriz de valores resueltos o rechaza cuando alguno de ellos rechaza . Tal vez solo quiera recuperar los resultados de todas las promesas y filtrar las rechazadas:

 const Fail = function(details){this.details=details;}, isFail = item => (item && item.constructor)===Fail; Promise.all( urls.map(//map array of urls to array of promises that don't reject url => fetch(url) .then( undefined,//do not handle resolve yet //when you handle the reject this ".then" will return // a promise that RESOLVES to the value returned below (new Fail([url,err])) err=>new Fail([url,err]) ) ) ) .then( responses => { console.log("failed requests:"); console.log( responses.filter(//only Fail type isFail ) ); console.log("resolved requests:"); console.log( responses.filter(//anything not Fail type response=>!isFail(response) ) ); } ); 

Su pregunta y los comentarios sugieren que podría usar un pequeño empujón de intuición sobre la forma en que funciona el ciclo de eventos. Realmente es confuso al principio, pero después de un tiempo se convierte en una segunda naturaleza.

En lugar de pensar en el VALOR FINAL, piense en el hecho de que tiene un solo hilo y no puede detenerlo, por lo que desea el VALOR FUTURO, el valor en el próximo o algún bucle de evento futuro. Todo lo que escriba que no sea asíncrono va a suceder casi de inmediato: las funciones regresan con algún valor o no están definidas inmediatamente . No hay nada que puedas hacer. Cuando necesita algo de manera asincrónica, necesita configurar un sistema que esté listo para tratar con los valores asíncronos cuando regresen en algún momento en el futuro. Esto es lo que todos los eventos, devoluciones de llamada, promesas (y async / await) intentan ayudar. Si algunos datos son asincrónicos, simplemente no puede usarlos en el mismo ciclo de eventos.

Entonces, ¿Qué haces?

Si desea un patrón donde crea una instancia, llame a init() y luego a alguna función que lo siga procesando, simplemente necesita configurar un sistema que realice el procesamiento cuando lleguen los datos. Hay muchas formas de hacer esto. Aquí hay una forma que es una variación de tu clase:

 function someAsync() { console.log("someAsync called") return new Promise(resolve => { setTimeout(() => resolve(Math.random()), 1000) }) } class Posts { constructor(url) { this.ready = false this.data = "uninitilized" this.url = url } init() { this.data = someAsync() } time100() { // it's important to return the promise here return this.data.then(d => d * 100) } } let p = new Posts() p.init() processData(p) // called twice to illustrate point processData(p) async function processData(posts) { let p = await posts.time100() console.log("randomin * 100:", p) } 

NOTA: Donde sea que use await , tiene que estar dentro de una función async .

Mira la FIDDLE ACTUALIZADA

await myFunc() usar await myFunc() para obtener el valor que espera de getPostById porque una función asincrónica siempre devuelve una promesa.

Esto a veces es muy frustrante ya que toda la cadena necesita convertirse en funciones async , pero ese es el precio que pagas por convertirlo a un código síncrono, supongo. No estoy seguro si eso se puede evitar, pero estoy interesado en saber de personas que tienen más experiencia en esto.

Pruebe el siguiente código en su consola copiando sobre las funciones y luego accediendo a la final y await final .

NOTA:

Una función asíncrona PUEDE contener una expresión de espera. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

No hay ninguna regla que deba haber esperado para incluso declarar una función asíncrona. El siguiente ejemplo utiliza una función asíncrona sin esperar solo para mostrar que una función asíncrona siempre devuelve una promesa.

 const sample = async () => { return 100; } // sample() WILL RETURN A PROMISE AND NOT 100 // await sample() WILL RETURN 100 const init = async (num) => { return new Promise((resolve, reject) => { resolve(num); }); } const myFunc = async (num) => { const k = await init(num); return k; } // const final = myFunc(); // final; This returns a promise // await final; This returns the number you provided to myFunc