J'obtiens une erreur qui dit

(node:27301) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Callback was already called.

D'après ce que je comprends du rejet des promesses dans Wait's et selon la description de Mozilla:

Si la promesse est rejetée, l'expression d'attente renvoie la valeur rejetée.

Je rejette l'erreur dans le rappel qui entoure ma promesse comme suit:

Airport.nearbyAirports = async (location, cb) => {
  let airports
  try {
    airports = await new Promise((resolve, reject) => {
      Airport.find({
        // code
      }, (err, results) => {
        if (err)
          reject(err) // Reject here
        else
          resolve(results)
      })
    })
  } catch (err) { // Catch here
    cb(err, null)
    return
  }
  if (!airports.empty)
    cb(null, airports)
  }

Ma question est

  1. Pourquoi considère-t-il toujours mon rejet de promesse comme non géré? J'ai pensé que l'instruction catch devrait faire taire cette erreur.
  2. Pourquoi considère-t-il que mon rappel est déjà appelé? J'ai une instruction return dans mon catch, donc les deux ne devraient jamais être appelés.
3
Huy-Anh Hoang 16 nov. 2017 à 03:22

4 réponses

Meilleure réponse

Le problème était en fait mon framework (LoopbackJS), pas ma fonction. Apparemment, au moment de la rédaction de cet article, l'utilisation de promesses n'est pas prise en charge:

https://loopback.io/doc/en/lb3/Using-promises.html#setup

Cela signifie que je ne peux même pas utiliser await dans ma fonction parce que la méthode distante encapsule ma fonction ailleurs, donc async serait toujours non géré. J'ai fini par revenir à une implémentation basée sur Promise du code interne:

Airport.nearbyAirports = (location, cb) => {
const settings = Airport.dataSource.settings
const db = DB(settings)
let airports
NAME_OF_QUERY().then((res) => {
  cb(null, res)
}).catch((err) => {
  cb(err, null)
})
6
Huy-Anh Hoang 23 nov. 2017 à 00:31

Si Airport.find() lève une exception, l'exécution passera à votre bloc catch et votre Promise ne sera jamais résolu ou rejeté. Vous devez peut-être l'envelopper dans son propre try/catch:

Airport.nearbyAirports = async (location, cb) => {
  let airports
  try {
    airports = await new Promise((resolve, reject) => {
      try {
        Airport.find({
          // code
        }, (err, results) => {
          if (err)
            reject(err) // Reject here
          else
            resolve(results)
        })
      } catch (err) {
        reject(err) // Reject here too
        cb(err, null)
      }
    })
  } catch (err) { // Catch here
    cb(err, null)
    return
  }
  if (!airports.empty)
    cb(null, airports)
  }
2
Rob Johansen 17 nov. 2017 à 20:57

Nous utilisons Loopback 2.31.0 et il prend également en charge le retour simple pour les fonctions asynchrones utilisées pour les méthodes distantes. Si vous placez un point d'arrêt quelque part dans votre méthode distante et que vous sautez d'un niveau au-dessus dans la pile d'appels, vous verrez comment il est implémenté dans le bouclage lui-même (shared-method.js):

  // invoke
  try {
    var retval = method.apply(scope, formattedArgs);
    if (retval && typeof retval.then === 'function') {
      return retval.then(
        function(args) {
          if (returns.length === 1) args = [args];
          var result = SharedMethod.toResult(returns, args);
          debug('- %s - promise result %j', sharedMethod.name, result);
          cb(null, result);
        },
        cb // error handler
      );
    }
    return retval;
  } catch (err) {
    debug('error caught during the invocation of %s', this.name);
    return cb(err);
  }
};

Ce qu'il fait ici - il appelle votre fonction et s'il s'agit d'une fonction asynchrone - il renverra une promesse (retval.then === 'function' sera true). Dans ce cas, le bouclage gérera correctement votre résultat, comme une promesse. Il effectue également la vérification des erreurs pour vous, de sorte que vous n'essayez plus / attrapez plus de blocs dans votre code.

Donc, dans votre propre code, il vous suffit de l'utiliser comme ci-dessous:

Airport.nearbyAirports = async (location) => {
    let airports = await new Promise((resolve, reject) => {
        Airport.find({
            // code
        }, (err, results) => {
            if (err)
                reject(err) // Reject here
            else
                resolve(results)
        })
    });

    if (!airports.empty)
        return airports;
    }
    else  {
        return {}; // not sure what you would like to return here as it wan not handled in your sample...
    }
}

Notez que vous n'avez pas du tout besoin d'utiliser le callback (cb) ici.

1
Peter Liapin 7 sept. 2018 à 18:19

Comme dit ici, le bouclage 3 prend en charge cela en vous permettant d'utiliser un retour simple.

Cette :

Entry.findFooById = async (id, cb) => {
  const result = await Entry.findById(id);
  return result;
};

...Est équivalent à :

Entry.findFooById = (id, cb) => {
  Entry.findById(id)
    .then(result => cb(null, result))
    .catch(cb);
};
1
adari 13 févr. 2018 à 10:25
47319554