3
votes

NodeJS Express asynchrone / attente

Toujours comprendre la nature non bloquante de Node. Le code suivant s'exécute comme prévu. Cependant, je me demande s'il existe une meilleure approche pour accomplir la tâche.

Il y a 3 paramètres fournis à l'itinéraire (code postal, type, rad). À partir de là, j'utilise le package NPM Zipcode pour renvoyer un tableau de codes postaux dans le rad fourni.

J'utilise alors une boucle for sur le tableau zips dans une fonction asynchrone et j'attends la réponse d'une fonction qui exécute une requête MySQL et renvoie une promesse. Puis renvoyer un tableau d'objets utilisateur.

Mon incertitude est de savoir si j'envoie correctement la réponse ou s'il existe un moyen plus efficace d'écrire ce code.

Merci .

router.get('/:zipcode/:type/:rad', function (req, res) {

  const rad = req.params.rad;
  const zip = req.params.zipcode;
  let zips = zipcodes.radius(zip, rad);
  zips.push(zip);

  let type;
  if(req.params.type === 'bartenders') {
    type = 0;
  } else {
    type = 1;
  }

  const params = {
    'type': type,
    'zips': zips
  };


  function userGroup(type, zip) {
    return new Promise(resolve => {
      connection.query(`SELECT * FROM bt9.users WHERE zip = ${zip} AND type = ${type} AND display = 1`, function (err, result) {
        if(err) throw err;
        resolve(result);
      });
    });
  }


  async function getUsers(params) {
    let userList = [];
    for (i = 0; i < params.zips.length; i++) {
      const users = await userGroup(params.type, params.zips[i]);
      for (u = 0; u < users.length; u++) {
        userList.push(users[u]);
      }
    }
    return userList;
  }


  function sendUsers(callback) {
    getUsers(params).then( res => {
      callback(null, res)
    })
  }


  sendUsers(function(err, result) {
    if(err) throw err;
    res.send(result)
  })


});


0 commentaires

3 Réponses :


1
votes

Vous ne devez pas générer d'erreur lorsque vous n'êtes pas dans une fonction asynchrone.

function userGroup(type, zip) {
    return new Promise( (resolve,reject) => {
      connection.query(`SELECT * FROM bt9.users WHERE zip = ${zip} AND type = ${type} AND display = 1`, function (err, result) {
        if(err) return reject(err); //<- reject and return
        resolve(result);
      });
    });
  }

De plus, vous pouvez utiliser Promise.all avec un tableau de promesses à la place de wait à l'intérieur de chaque itération de boucle. Cela permettrait une exécution parallèle à votre connexion.


0 commentaires

0
votes

Pour compléter la réponse de Steven Spungin , voici la fonction getUsers refactorisée avec Promise.all :

const aaf = require('async-af');
// ...
async function getUsers({zips, type}) {
  const userGroups = await aaf(zips).map(zip => userGroup(type, zip));
  return userGroups.flat(); // flat isn't yet part of 'async-af' but now that it's finalized for ES, I'm sure it'll be added soon.
}

Sinon, si cela ne vous dérange pas d'utiliser un module tiers, vous pouvez ajouter async-af et utilisez son mapAF (alias map ) méthode:

function getUsers({zips, type}) {
  return Promise.all(zips.map(zip => userGroup(type, zip)))
    .then(users => users.flat());
}


0 commentaires

3
votes

Au lieu de convertir manuellement chaque fonction de rappel en Promise, il est plus facile de créer un wrapper pour prendre en charge async / await.

Référence
https://strongloop.com/strongblog/async-error -handling-expressjs-es7-promesses-generators /

async function getUsers({ type, zips }) {
  let userList = []
  const userListList = await Promise.map(zips, (zip) => {
    return userGroup(type, zip);
  });
  for (let users of userListList) {
    userList = userList.concat(users)
  }
  return userList;
}

Exemple de code

function userGroup(type, zip) {
  return new Promise(resolve => {
    connection.query(`SELECT * FROM bt9.users WHERE zip = ${zip} AND type = ${type} AND display = 1`, function (err, result) {
      if(err) throw err;
      resolve(result);
    });
  });
}

async function getUsers({ type, zips }) {
  let userList = [];
  // [IMPORTANT]
  // - Replaced the for-loop to for-of-loop for await to work.
  // - This is not efficient because the `userGroup` function is run one by one serially.
  for (let zip of zips) {
    const users = await userGroup(type, zip);
    userList = userList.concat(users);
  }
  return userList;
}

router.get('/:zipcode/:type/:rad', asyncWrapper(async (req) => {
  const rad = req.params.rad;
  const zip = req.params.zipcode;
  let zips = zipcodes.radius(zip, rad);
  zips.push(zip);

  let type;
  if(req.params.type === 'bartenders') {
    type = 0;
  } else {
    type = 1;
  }

  return await getUsers({ type, zips });
}));

Refactorisation de votre code

async createUser(req) {
  const user = await User.save(req.body)
  return user
}

router.post('/users', asyncWrapper(createUser))

Pour améliorer encore l'efficacité, vous devez remplacer le for-of-loop dans getUsers par Promise.map proposé par bluebird . Promise.map exécutera les promesses en parallèle.

function asyncWrapper(fn) {
  return (req, res, next) => {
    return Promise.resolve(fn(req))
      .then((result) => res.send(result))
      .catch((err) => next(err))
  }
}


0 commentaires