Como “esperar” o retorno de uma chamada?

102

Ao usar um retorno de chamada simples, como no exemplo abaixo:

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

Como a função pode ser alterada para usar async / await? Especificamente, supondo que 'someEvent' seja chamado uma vez e apenas uma vez, gostaria que o teste de função fosse uma função assíncrona que não retorna até que o retorno de chamada seja executado, como:

async test() {
  return await api.on( 'someEvent' );
}
sean2078
fonte
1
Apenas para referência, a especificação ES7 / ES2016 foi finalizada e não inclui async / await. No momento, é apenas uma proposta de estágio 3 .
Dan Prince
Bem, isso é surpreendente - espero que seja incluído! Obrigado pela informação @DanPrince
sean2078

Respostas:

150

async/awaitnão é mágica. Uma função assíncrona é uma função que pode desembrulhar Promises para você, então você precisará api.on()retornar uma Promise para que funcione. Algo assim:

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

Então

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

Mas isso também é uma mentira, porque as funções assíncronas também retornam promessas, então você não vai realmente obter o valor de test(), mas sim uma promessa de um valor, que você pode usar assim:

async function whatever() {
  // snip
  const response = await test();
  // use response here
  // snip
}
Fantasma de madara
fonte
3
Uma versão mais curta para a função que retorna uma promessa: const apiOn = (event) => new Promise(resolve => api.on(event, resolve));
Felipe Plets
7

É irritante que não haja uma solução direta, e embrulhar return new Promise(...)é feio, mas eu encontrei uma solução ok para usar util.promisify(na verdade ele meio que faz o mesmo embrulho, só parece melhor).

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

A função acima não retorna nada, ainda. Podemos fazer com que ele retorne um Promisedo responsepassado callback, fazendo:

const util = require('util');

const asyncFunction = util.promisify(voidFunction);

Agora podemos realmente awaito callback.

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

Algumas regras ao usar util.promisify

  • A callbackdeve ser o último argumento da função que vai serpromisify
  • O suposto retorno de chamada deve estar no formato (err, res) => {...}

O engraçado é que não precisamos nunca escrever especificamente o que callbackrealmente é.

ErikD
fonte
3

async / await é mágico. Você pode criar uma função asPromisepara lidar com este tipo de situação:

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

e use-o quando quiser:

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

o número de args é variável.

negstek
fonte
1

Você pode conseguir isso sem retornos de chamada, use a promessa async await em vez de retornos de chamada aqui, como eu faria isso. E também aqui ilustrei dois métodos para lidar com erros

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
fonte