¿Cómo "esperar" a que vuelva una devolución de llamada?

100

Cuando se utiliza una devolución de llamada simple como en el siguiente ejemplo:

test() {
  api.on( 'someEvent', function( response ) {
    return response;
  });
}

¿Cómo se puede cambiar la función para usar async / await? Específicamente, asumiendo que se garantiza que 'someEvent' se llamará una vez y solo una vez, me gustaría que la prueba de función sea una función asíncrona que no regrese hasta que se ejecute la devolución de llamada, como por ejemplo:

async test() {
  return await api.on( 'someEvent' );
}
sean2078
fuente
1
Solo como referencia, la especificación ES7 / ES2016 se ha finalizado y no incluye async / await. Por el momento es solo una propuesta de etapa 3 .
Dan Prince
Bueno, eso es sorprendente, ¡espero que se incluya! Gracias por la información @DanPrince
sean2078

Respuestas:

146

async/awaitno es magia. Una función asincrónica es una función que puede desenvolver promesas por usted, por lo que api.on()deberá devolver una promesa para que funcione. Algo como esto:

function apiOn(event) {
  return new Promise(resolve => {
    api.on(event, response => resolve(response));
  });
}

Entonces

async function test() {
  return await apiOn( 'someEvent' ); // await is actually optional here
                                      // you'd return a Promise either way.
}

Pero eso también es una mentira, porque las funciones asíncronas también devuelven Promesas en sí mismas, por lo que en realidad no obtendrá el valor test(), sino una Promesa por un valor, que puede usar así:

async function whatever() {
  // snip
  const response = await test();
  // use response here
  // snip
}
Fantasma de Madara
fuente
3
Una versión más corta de la función que devuelve una promesa: const apiOn = (event) => new Promise(resolve => api.on(event, resolve));
Felipe Plets
7

Es molesto que no haya una solución sencilla, y la envoltura return new Promise(...)es fea, pero he encontrado una solución alternativa aceptable util.promisify(en realidad, también hace la misma envoltura, solo que se ve mejor).

function voidFunction(someArgs, callback) {
  api.onActionwhichTakesTime(someMoreArgs, (response_we_need) => {
    callback(null, response_we_need);
  });
}

La función anterior no devuelve nada todavía. Podemos hacer que devuelva uno Promisede los responsepasados callbackhaciendo:

const util = require('util');

const asyncFunction = util.promisify(voidFunction);

Ahora podemos realmente awaitel callback.

async function test() {
  return await asyncFunction(args);
}

Algunas reglas al usar util.promisify

  • El callbackdebe ser el último argumento de la función que va a serpromisify
  • La supuesta devolución de llamada debe tener el formato (err, res) => {...}

Lo curioso es que no necesitamos escribir nunca específicamente lo que callbackrealmente es.

ErikD
fuente
3

async / await es mágico. Puedes crear una función asPromisepara manejar este tipo de situaciones:

function asPromise(context, callbackFunction, ...args) {
    return new Promise((resolve, reject) => {
        args.push((err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
        if (context) {
            callbackFunction.call(context, ...args);
        } else {
            callbackFunction(...args);
        }
    });
}

y luego úsalo cuando quieras:

async test() {
    return await this.asPromise(this, api.on, 'someEvent');
}

el número de argumentos es variable.

negstek
fuente
1

Puede lograr esto sin devoluciones de llamada, use promise async await en lugar de devoluciones de llamada aquí cómo lo haría. Y también aquí he ilustrado dos métodos para manejar errores.

clickMe = async (value) => {
  
  // begin to wait till the message gets here;
  let {message, error} = await getMessage(value);
  
  // if error is not null
  if(error)
    return console.log('error occured ' + error);
   
  return console.log('message ' + message);

}

getMessage = (value) => {

  //returning a promise 
  return new Promise((resolve, reject) => {
  
    setTimeout(() => {
      // if passed value is 1 then it is a success
      if(value == 1){
        resolve({message: "**success**", error: null});
      }else if (value == 2){
        resolve({message: null, error: "**error**"});
      }
    }, 1000);
  
  });

}

clickWithTryCatch = async (value) => {

  try{
    //since promise reject in getMessage2 
    let message = await getMessage2(value);
    console.log('message is ' + message);
  }catch(e){
    //catching rejects from the promise
    console.log('error captured ' + e);
  }

}

getMessage2 = (value) => {

  return new Promise((resolve, reject) => {
  
    setTimeout(() => {
      if(value == 1)
        resolve('**success**');
      else if(value == 2)
        reject('**error**'); 
    }, 1000);
  
  });

}
<input type='button' value='click to trigger for a value' onclick='clickMe(1)' />
<br/>
<input type='button' value='click to trigger an error' onclick='clickMe(2)' />
<br/>
<input type='button' value='handling errors with try catch' onclick='clickWithTryCatch(1)'/>
<br/>
<input type='button' value='handling errors with try catch' onclick='clickWithTryCatch(2)'/>

NuOne
fuente