Comment «attendre» le retour d'un rappel?

100

Lors de l'utilisation d'un simple rappel comme dans l'exemple ci-dessous:

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

Comment la fonction peut-elle être modifiée pour utiliser async / await? Plus précisément, en supposant que `` someEvent '' soit appelé une et une seule fois, j'aimerais que le test de fonction soit une fonction asynchrone qui ne retourne pas tant que le rappel n'est pas exécuté, par exemple:

async test() {
  return await api.on( 'someEvent' );
}
sean2078
la source
1
Juste pour référence, la spécification ES7 / ES2016 a été finalisée et n'inclut pas async / await. Pour le moment, il ne s'agit que d'une proposition de l'étape 3 .
Dan Prince
Eh bien, c'est surprenant - J'espère beaucoup qu'il sera inclus! Merci pour l'info @DanPrince
sean2078

Réponses:

146

async/awaitn'est pas magique. Une fonction asynchrone est une fonction qui peut dérouler des promesses pour vous, vous devrez api.on()donc renvoyer une promesse pour que cela fonctionne. Quelque chose comme ça:

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

ensuite

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

Mais c'est aussi un mensonge, car les fonctions asynchrones renvoient également des promesses elles-mêmes, vous n'allez donc pas en tirer la valeur test(), mais plutôt une promesse pour une valeur, que vous pouvez utiliser comme ceci:

async function whatever() {
  // snip
  const response = await test();
  // use response here
  // snip
}
Fantôme de Madara
la source
3
Une version plus courte pour la fonction qui renvoie une promesse: const apiOn = (event) => new Promise(resolve => api.on(event, resolve));
Felipe Plets
7

C'est ennuyeux qu'il n'y ait pas de solution simple, et l'emballage return new Promise(...)est difficile, mais j'ai trouvé un moyen de contourner le problème util.promisify(en fait, il fait aussi un peu le même emballage, c'est juste plus joli).

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

La fonction ci-dessus ne renvoie encore rien. On peut lui faire retourner un Promisedu responsepassé en callbackfaisant:

const util = require('util');

const asyncFunction = util.promisify(voidFunction);

Maintenant, nous pouvons réellement awaitle callback.

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

Quelques règles lors de l'utilisation util.promisify

  • Le callbackdoit être le dernier argument de la fonction qui va êtrepromisify
  • Le rappel supposé doit être au format (err, res) => {...}

Ce qui est drôle, c'est que nous n'avons jamais besoin d'écrire spécifiquement ce callbackqui est réellement.

ErikD
la source
3

async / await est magique. Vous pouvez créer une fonction asPromisepour gérer ce genre de situations:

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

puis utilisez-le quand vous le souhaitez:

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

le nombre d'arguments est variable.

Negstek
la source
1

Vous pouvez y parvenir sans callback, utilisez promise async await au lieu de callbacks ici comment je le ferais. Et aussi ici j'ai illustré deux méthodes pour gérer les erreurs

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
la source