Les Promises simplifient grandement l’écriture et la lecture des traitements asynchrones. Il n’en reste pas moins que certains cas d’usage ne sont pas aussi évidents que ça. Dans cette période d’apprentissage de node, l’es6, et l’implémentation quasi systématique de Promises, je suis tombé sur un de ces cas: les promises dans les boucles while. Et j’ai trouvé assez peu de littérature sur le sujet.

Problématique

Une promise effectue un test asynchrone, et tant que son résultat n’est pas celui escompté, je dois réitérer la promise.

Exemple concert : je créé un utilisateur en base dont la propriété username doit être unique. Si le username existe déjà en base, je le suffixe d’un nombre s’incrémentant jusqu’à ce que ce username n’existe pas en base et soit donc unique.
Ma requête en base indiquant si un username existe renvoi une promise.

Si checkUsernameExists était une fonction synchrone et revoyait simplement un booléen, il suffirait d’écrire le code suivant :


var username = 'toto';
var inc = 0;
var usernameTest = username;
while( checkUsernameExists(usernameTest) ) {
inc++;
usernameTest = username + '_' + inc;
}
// => usernameTest est unique

Mais si ma fonction checkUsernameExists renvoi une promise, ça se complique. Exemple de la fonction:


function checkUsernameExists(username) {
return new Promise( (resolve, reject) => {
// ... database query stuff
resolve(exists ? true : false);
});
}

setImmediate au secours de While Promise

La solution n’est pas évidente de prime abord, et c’est Christophe qui me l’a très gentillement soufflée. Après l’avoir lue plusieurs fois, elle devient simple – limite évidente :)

L’astuce qui me manquait cruellement était le setImmediate !


function findUniqueUsername(username) {
var usernameBase = username;
var inc = 0;
return new Promise( (resolve, reject) => {
setImmediate(function myPromise() {
return checkUsernameExists(username)
.then( usernameFoundInDb => {
if (usernameFoundInDb) {
inc++;
username = usernameBase + '_' + inc;
setImmediate(myPromise);
} else {
resolve(username);
}
})
.catch(err => {
reject(err);
});
});
}

Implémentation


findUniqueUsername('toto').then( uniqueUsername => {
// uniqueUsername will be "toto" or "toto_1", "toto_2", ... unique!
});

Pour aller plus loin

En recherchant et appliquant cette solution, j’ai découvert plusieurs lectures relatives qui pourrait compléter cet article:
– Choisir entre setImmediate et NextTick;
– Les timers en node;