Méthode asynchrone Node.js

Default
,

Bonjour,

J'essaie de développer une méthode permettant de vérifier si le nom de mon nouveau fichier à uploader existe déjà dans ma base de donnée. S'il existe déjà, je souhaite l'incrémenter, voici un exemple :

illustration-pleine-lune.jpg
illustration-pleine-lune-1.jpg
illustration-pleine-lune-2.jpg
illustration-pleine-lune-3.jpg

Pour développer cela, j'ai pensé à utiliser un appel récursive : tant qu'on trouve un fichier du même nom, on incrémente le nom du nouveau fichier uploadé. Ma méthode semble fonctionner correctement, or celle-ci n'a pas le temps de finir de s'executer que Node.js traite la suite des opérations. Mes fichiers sont donc souvent undefined car la variable basename n'a pas le temps d'être assignée. Je me suis documentée et il semblerai qu'il soit nécessaire d'utiliser une méthode asynchrone, à l'aide du module async. Voici ma méthode avec async.

var async = require('async');

function checkUniqueFilename (title, extension, callback) {

  var iterator = 0,
  basename = slug(title, { lower: true }), // slugify le titre du fichier : illustration-pleine-lune
  filename = basename + '.' + extension; // illustration-pleine-lune.jpg

  async.waterfall([

    function doFindOne (filename, callback) {

      Media.findOne({ filename: filename }, function (err, media) {
        if (err)
          callback(err, null);

        if (!media)
          callback(null, basename); // Filename inexistant, donc non renommé : ARRET, renvoi de la valeur de basename

        // Un média a été trouvé, incrémentation pour renommer le nouveau fichier
        iterator++;

        if (iterator > 1)
          basename = basename.substr(0, basename.lastIndexOf('-')); // Basename d'origine, suppression de "-nombre"

        basename += '-' + iterator; // illustration-pleine-lune-1
        filename = basename + extension; // illustration-pleine-lune-1.jpg

        doFindOne(filename, callback); // Appel récursive jusqu'à ne pas trouver de média
      })

    }],

    function (err, basename) {
      if (err)
        throw err;

      return basename;
    }
  );
}

Utilisation de cette méthode :

exports.uploading = function (req, res, next) {
    var title = 'Illustration pleine lune',
        extension = '.jpg',
        basename = checkUniqueFilename(title, extension, function(response) {
            return response.toString();
      }),
      filename = basename + '.' + extension;
}

L'erreur me renvoie :
callback(null, basename); n'est pas une fonction. Et basename ne prend pas la valeur de retour, il reste undefined;

Il s'agit pourtant d'une méthode relativement simple, mais je n'arrive pas à comprendre mon erreur.

Je fais appel à la communauté Grafikart pour m'aiguiller dans la résolution de ce problème.

Je vous remercie par avance pour votre aide précieuse.

6 Réponse

17162
,

Bonjour.
Je ne voudrais pas dire de bêtise, mais est-ce normal que tu n'aies pas donné de nom à cette fonction ? :

function (err, basename) {
      if (err)
        throw err;

      return basename;
    }

Car l'erreur me semble tout à fait y faire référence.

Default
,

Bonjour Lartak,

Il semblerai d'après la doc Async.js qu'on puisse ne pas nommer certaines functions. J'ai choisi de nommer la première pour pouvoir la rappeler récursivement, mais je me trompe peut-être.

J'ai du mal à saisir le fonctionnement des méthodes asynchrones, quand je fais appel à uploading :

exports.uploading = function (req, res, next) {
    var title = 'Illustration pleine lune',
        extension = '.jpg',
        basename = checkUniqueFilename(title, extension, function(response) {
            console.log('Le basename : ' + response);
            return response.toString();
      }),
      filename = basename + '.' + extension;
      console.log('Le filename : ' + filename);
}

filename fait la concaténation des deux variables avant même d'avoir la valeur de basename. J'ai donc ceci dans la console :

Le filename : undefined.jpg
Le basename : illustration-pleine-lune

Alors qu'ici je cherche à ce que la méthode checkUniqueFilename s'exécute, renvoi son résultat à la variable basename PUIS que filename fasse la concaténation des deux variables. Je suis certaine qu'il s'agit d'une erreur de compréhension (de débutante), or il semblerai qu'il y ai peu de personnes maitrisant Node.js sur le forum.

Merci pour ton aide Lartak.

17162
,

Merci pour ton aide Lartak.

De rien, mais je ne suis pas tellement un adepte de Node.js, du coup je n'ai fais que relever un point qui me semblait bizarre, mais attend un jour ou plus, car il y en a sur le site qui s'y connaissent bien sur Node.js et qui pourront bien mieux t'aider.
Sur ce, bonne continuation à toi et désolé de ne pouvoir t'aider plus que ça.

9427
,

Je n'ai pas regardé le détail de ta fonction asynchrone, mais il faut cependant prendre en compte quelques remarques :
Lorsque tu fais

basename = checkUniqueFilename(title, extension, function(response) {
   return response.toString();
})

basename prend le retour de checkUniqueFilename, soit undefined vu que cette fonction ne retourne rien, et le return de ta fonction anonyme n'est jamais pris en compte.
De plus, quand bien même cette forme marcherait, le reste de la fonction s'exécuterait AVANT que basename soit défini (cat checkUniqueFilename est asynchrone du coup)

Autre note, la fonction pourrait être simplifiée en faisant un seul Media.find() (les requêtes mongoose marchent avec des regexp), ça te renverra un tableau que tu pourras traiter plus facilement :)

Je sais que je ne réponds pas vraiment à la question, mais en corrigeant ces quelques points, tu devrais peut-être arriver à quelque chose de fonctionnel

Default
,

Merci Adrien pour tes éclaircissements.

Je débute avec Node.js et je n'ai pas encore compris tous les mécanismes. Je suis heureuse de voir qu'il y a des gens comme toi sur ce forum qui semblent très bien maitriser cette technologie !

Je prends en compte ta remarque de simplification, je viens de me documenter je ne savais pas qu'on pouvait utiliser les expressions régulières avec mongoose.

Dois-je m'attendre à quelque chose comme ceci ?

function checkUniqueFilename (title, extension) {
    var basename = slug(title, { lower: true }), // slugify le titre du fichier : illustration-pleine-lune
    filename = basename + '.' + extension;
    Media.find({ filename: { $regex: filename } }, function (err, media) {
        if (err)
            return err;

        if (!media)
            return basename;

        // On a trouvé illustration-pleine-lune, illustration-pleine-lune-1, illustration-pleine-lune-5 et illustration-pleine-lune-8
        // Je cherche à faire un return illustration-pleine-lune-9, mais comment ?
    });
}

Ou alors le find est censé me renvoyer un tableau, que je stock dans une variable ?

function checkUniqueFilename (title, extension) {
    var basename = slug(title, { lower: true }), // slugify le titre du fichier : illustration-pleine-lune
    filename = basename + '.' + extension,
    filenames = Media.find({ filename: { $regex: filename } });

    // comment gérer le traitement avec les tableaux ? filenames[0] ?
}

Et à ce moment là je pourrai récupérer le return de ma fonction dans basename ?

exports.uploading = function (req, res, next) {
    var title = 'Illustration pleine lune',
        extension = '.jpg',
        basename = checkUniqueFilename(title, extension),
        filename = basename + '.' + extension;
}

Peux-tu me dire si je suis sur la piste ou pas du tout.

Merci de ton aide.

9427
,

Bonjour,

findfonctionne comme findOneavec une fonction de callback qui prend deux paramètres : err et un tableau (de medias dans ton cas)
Pour le find avec une regex, tu peux l'utiliser exactement comme avec une chaîne de caractère :

Media.find({
  filename: regex
}, function (err, medias) {
  // Ton code
});